{
 "openapi": "3.1.0",
 "info": {
  "title": "Assure check answer",
  "version": "1",
  "description": "Focused description of the check answer: POST /v1/checks and GET /v1/checks/{check_id}, and the profile listing they take their profile ids and file names from, GET /v1/profiles (issue #2072). The verdict's own fields are described in VERDICTS.md; this contract names the envelope and the optional upgrade object a free account's answer can carry."
 },
 "servers": [
  {
   "url": "https://api.symbolia.ai"
  }
 ],
 "security": [
  {
   "bearerAuth": []
  }
 ],
 "paths": {
  "/v1/checks": {
   "post": {
    "operationId": "submitCheck",
    "description": "Submit collected files for one profile (QUICKSTART-API.md, section 4). 200 with the verdict when the check finishes within 20 seconds of the request (its upload and admission included; an admission past 19.5 seconds leaves the check half a second), else 202 with the check id to poll.",
    "responses": {
     "200": {
      "$ref": "#/components/responses/Verdict"
     },
     "202": {
      "$ref": "#/components/responses/Running"
     },
     "default": {
      "$ref": "#/components/responses/Failure"
     }
    }
   }
  },
  "/v1/checks/{check_id}": {
   "get": {
    "operationId": "getCheck",
    "parameters": [
     {
      "name": "check_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^[0-9a-f]{32}$"
      }
     }
    ],
    "description": "The verdict of one of your checks, readable for 30 days; 202 while it runs.",
    "responses": {
     "200": {
      "$ref": "#/components/responses/Verdict"
     },
     "202": {
      "$ref": "#/components/responses/Running"
     },
     "default": {
      "$ref": "#/components/responses/Failure"
     }
    }
   }
  },
  "/v1/profiles": {
   "get": {
    "operationId": "listProfiles",
    "description": "The served profiles, each with its input allow-list, byte caps and fail_on words, plus the global limits, the engine that answers, the planned profiles not served and the route table (QUICKSTART-API.md, section 6, the GET /v1/profiles row). Free.",
    "responses": {
     "200": {
      "description": "The profile listing",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/ProfileListing"
        }
       }
      }
     },
     "default": {
      "$ref": "#/components/responses/Failure"
     }
    }
   }
  }
 },
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer"
   }
  },
  "responses": {
   "Verdict": {
    "description": "The check finished",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Verdict"
      }
     }
    }
   },
   "Running": {
    "description": "The check is still running",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Running"
      }
     }
    }
   },
   "Failure": {
    "description": "A typed outcome (QUICKSTART-API.md, section 9)",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Failure"
      }
     }
    }
   }
  },
  "schemas": {
   "Verdict": {
    "type": "object",
    "required": [
     "schema",
     "check_id",
     "profile",
     "outcome",
     "policy"
    ],
    "additionalProperties": true,
    "description": "The verdict envelope (VERDICTS.md). Fields not listed here are described there.",
    "properties": {
     "charged": {
      "type": "boolean",
      "description": "The check accounting flag on newly written server answers. Historical stored answers may omit it; a read-back preserves their bytes."
     },
     "schema": {
      "const": "assure.serve.verdict/v1"
     },
     "check_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{32}$"
     },
     "profile": {
      "type": "string"
     },
     "outcome": {
      "type": "string"
     },
     "policy": {
      "type": "object",
      "required": [
       "exit"
      ],
      "properties": {
       "exit": {
        "type": "integer",
        "minimum": 0,
        "maximum": 125
       },
       "fail_on": {
        "type": "array",
        "items": {
         "type": "string"
        }
       },
       "reason": {
        "type": "string"
       }
      }
     },
     "upgrade": {
      "$ref": "#/components/schemas/Upgrade"
     },
     "not_established": {
      "type": "array",
      "description": "One entry per obligation not established (VERDICTS.md section 9).",
      "items": {
       "type": "object",
       "additionalProperties": true,
       "properties": {
        "because": {
         "type": "object",
         "required": [
          "gate",
          "premise",
          "source"
         ],
         "description": "Issue #1813, on postgresql-observed-baseline and python-auth-declared-model: which input sent the entry here. gate machine: the machine was refused or not read (premise the refusal reason, source the raw file it names); gate reading: the reading was not established (premise the declaration field or unobserved premise, an informational premise skipped, source raw/declaration.json#<field> or the raw file). A part nothing names is null.",
         "properties": {
          "gate": {
           "enum": [
            "machine",
            "reading"
           ]
          },
          "premise": {
           "type": [
            "string",
            "null"
           ]
          },
          "source": {
           "type": [
            "string",
            "null"
           ]
          }
         }
        }
       }
      }
     }
    }
   },
   "Upgrade": {
    "type": "object",
    "required": [
     "url",
     "line"
    ],
    "additionalProperties": false,
    "description": "Present only when the account is on the free plan and the server has a Premium payment link, or when a gifted plan ends within 30 days and the server has that link. Absent on every other paid plan; its presence changes no other field. The Action prints it as the first line of the job summary.",
    "properties": {
     "url": {
      "type": "string",
      "format": "uri",
      "pattern": "^https://buy\\.stripe\\.com/",
      "description": "The Premium Stripe Payment Link with your account id, URL-encoded, as client_reference_id"
     },
     "line": {
      "type": "string",
      "description": "One plain sentence that ends with url"
     },
     "notice": {
      "type": "string",
      "description": "Present only when the free plan's USD 1 balance, shared by reports and requirement compiling, is spent, or when a gifted plan ends within 30 days (\"Your gifted Premium plan ends on <date>.\", and line is \"To keep it, upgrade at <url>\"): one plain sentence, printed at the start of the upgrade line"
     }
    },
    "example": {
     "url": "https://buy.stripe.com/<link id>?client_reference_id=gh-acme",
     "line": "Assure Premium checks your app's sign-in and session routes, not only the database. Upgrade for USD 20 a month: https://buy.stripe.com/<link id>?client_reference_id=gh-acme"
    }
   },
   "Running": {
    "type": "object",
    "required": [
     "check_id",
     "status"
    ],
    "additionalProperties": false,
    "properties": {
     "check_id": {
      "type": "string"
     },
     "status": {
      "const": "running"
     }
    }
   },
   "Failure": {
    "type": "object",
    "required": [
     "schema",
     "outcome",
     "reason",
     "action"
    ],
    "additionalProperties": true,
    "properties": {
     "charged": {
      "type": "boolean",
      "description": "The check accounting flag on newly written server answers. Historical stored answers may omit it; a read-back preserves their bytes."
     },
     "schema": {
      "const": "assure.serve.failure/v1"
     },
     "request_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{16}$",
      "description": "The X-Request-Id of the request, on a refusal answered before a check was admitted (check_id null; issue #1101); absent when the envelope names a check_id."
     },
     "detail": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
       "served_from": {
        "enum": [
         "handler",
         "store"
        ],
        "description": "Where the API served this failure from (issue #1665): handler when minted while answering this request, store when it is the admitted check's stored failure, first answered or read back."
       }
      }
     },
     "outcome": {
      "type": "string"
     },
     "reason": {
      "type": "string"
     },
     "action": {
      "type": "string"
     },
     "build": {
      "type": "object",
      "required": [
       "commit",
       "release_sha256",
       "checkers_sha256"
      ],
      "properties": {
       "commit": {
        "type": [
         "string",
         "null"
        ]
       },
       "release_sha256": {
        "type": [
         "string",
         "null"
        ]
       },
       "checkers_sha256": {
        "type": [
         "string",
         "null"
        ]
       }
      },
      "description": "Issue #1100: every freshly API-built refusal carries the serving engine's identity, including checker_error that refuses a development verdict; never copy the refused development block, kind or tree_sha256. Exactly commit, release_sha256 and checkers_sha256, the three keys of a verdict's engine block; all three are null if the engine pin fails verification. Compare build.commit first. Historical stored and non-API failures can omit this optional outer property and remain as originally written.",
      "additionalProperties": false
     }
    }
   },
   "ProfileListing": {
    "type": "object",
    "required": [
     "profiles",
     "limits",
     "engine",
     "not_served",
     "routes",
     "image"
    ],
    "additionalProperties": true,
    "description": "The GET /v1/profiles answer (serve/api.py public_profiles). Keys not listed here may be added; none listed is removed.",
    "properties": {
     "build": {
      "type": "object",
      "required": [
       "commit",
       "release_sha256",
       "checkers_sha256"
      ],
      "properties": {
       "commit": {
        "type": [
         "string",
         "null"
        ]
       },
       "release_sha256": {
        "type": [
         "string",
         "null"
        ]
       },
       "checkers_sha256": {
        "type": [
         "string",
         "null"
        ]
       }
      },
      "description": "Issue #1100: the top-level serving engine identity, exactly commit, release_sha256 and checkers_sha256, the three keys of a verdict's engine block. Compare build.commit first to distinguish a deploy lag from a changed answer. All three values are null if the engine pin cannot be verified; no kind or tree_sha256 is served here. This block is beside profiles and limits and changes no profile row.",
      "additionalProperties": false
     },
     "profiles": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/ProfileRow"
      }
     },
     "limits": {
      "type": "object",
      "required": [
       "body_bytes",
       "file_bytes",
       "files",
       "json_depth"
      ],
      "additionalProperties": true,
      "properties": {
       "body_bytes": {
        "type": "integer"
       },
       "file_bytes": {
        "type": "integer"
       },
       "files": {
        "type": "integer"
       },
       "json_depth": {
        "type": "integer"
       }
      }
     },
     "engine": {
      "type": "object",
      "description": "{commit, release}: the build that answers; both null while /healthz answers 503 engine_digest_mismatch."
     },
     "not_served": {
      "type": "array",
      "description": "[{id, stack, waits_on}]: the planned profiles this server knows about and does not serve."
     },
     "routes": {
      "type": "array",
      "description": "[{template, methods, free, rate_window, reads}]: the route table this server matches."
     },
     "image": {
      "type": [
       "object",
       "null"
      ],
      "description": "{files, manifest_sha256} of the built tree the server runs from, or null in a checkout."
     },
     "served": {
      "type": "object",
      "description": "Bounded possible statuses per obligation, keyed by served profile id. Obligations follow the profile order and reachable statuses follow its precedence order. Profiles without computable obligations are omitted.",
      "additionalProperties": {
       "type": "object",
       "required": [
        "obligations"
       ],
       "properties": {
        "obligations": {
         "type": "array",
         "items": {
          "type": "object",
          "required": [
           "id",
           "reachable"
          ],
          "properties": {
           "id": {
            "type": "string"
           },
           "reachable": {
            "type": "array",
            "items": {
             "type": "string"
            }
           }
          }
         }
        }
       }
      }
     }
    }
   },
   "ProfileRow": {
    "type": "object",
    "required": [
     "id",
     "version",
     "inputs",
     "fail_on",
     "bias_default",
     "bias_classes",
     "obligations",
     "collector_pin",
     "progress_notes",
     "stacks"
    ],
    "additionalProperties": true,
    "description": "One served profile. The postgresql-declared-model row is pinned byte for byte (DIGEST-BASE) and carries stacks (issue #2650) but none of readings_shape, profile_sha256, collector, checker or docs. Keys not listed here may be added.",
    "properties": {
     "id": {
      "type": "string"
     },
     "version": {
      "description": "The profile row's version."
     },
     "inputs": {
      "type": "object",
      "required": [
       "required",
       "optional"
      ],
      "description": "required: [{path, max_bytes}]; optional: [{path or pattern, max_bytes}]. A pattern must match the whole name."
     },
     "fail_on": {
      "type": "object",
      "required": [
       "accepted",
       "default",
       "not_collected"
      ],
      "properties": {
       "excluded_by_design": {
        "type": "array",
        "items": {
         "type": "string"
        },
        "description": "The statuses the profile can emit that not_collected leaves out by design (needs_intent); absent from the postgresql-declared-model row."
       }
      },
      "description": "The words ?fail_on= takes for this profile, the default, the statuses not collected and the statuses excluded by design."
     },
     "bias_default": {
      "type": [
       "string",
       "null"
      ],
      "description": "refute or prove, or null when the checker declares no bias classes."
     },
     "bias_classes": {
      "type": [
       "object",
       "null"
      ]
     },
     "obligations": {
      "type": "array",
      "description": "[{id, machine, bias, security, observable_at_pin, class}]."
     },
     "collector_pin": {
      "type": [
       "string",
       "null"
      ]
     },
     "progress_notes": {
      "description": "No served derive emits one; the 202 poll is the signal."
     },
     "description": {
      "type": "string",
      "description": "The profile's words, when the row has them."
     },
     "canonical_statements": {
      "type": "array",
      "description": "The declared profiles' canonical statements."
     },
     "checker_entry_sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
     },
     "requirement_states": {
      "description": "mysql-declared-model only: the states each obligation can take."
     },
     "scope": {
      "type": "object",
      "description": "{max_ids, binding[, records]}: only for a profile whose scope you select."
     },
     "method_forms": {
      "type": "array",
      "description": "python-auth-declared-model only: [{form, lean_decidable, lean_decidable_when, consumers}]."
     },
     "stacks": {
      "type": [
       "array",
       "null"
      ],
      "description": "The identity stacks the registry wires to the profile's checker (for python-auth-declared-model also to the python/fastapi, python/django (issue #266) and python/stdlib-http (issue #3840) adapters its free route runs; for a checker that runs a shared identity adapter as its leg, that adapter's row, issue #3495), or the database/endpoint kind (database/postgresql, database/mysql, endpoint/http) for the PostgreSQL, MySQL and HTTP profiles; on every row, the pinned one included (issue #2650). null for a registry-wired row when the server holds no readable adapter registry."
     },
     "readings_shape": {
      "enum": [
       "wrapped",
       "flat"
      ],
      "description": "How scope_binding is sent; absent from the pinned row."
     },
     "profile_sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "The sha256 of the profile row file's bytes: the same digest a verdict for this profile carries as profile_sha256 (issue #2072). Absent from the pinned row."
     },
     "collector": {
      "type": "object",
      "required": [
       "status",
       "file"
      ],
      "additionalProperties": false,
      "properties": {
       "status": {
        "enum": [
         "pinned",
         "none",
         "malformed"
        ]
       },
       "file": {
        "type": [
         "string",
         "null"
        ]
       }
      },
      "description": "The registered collector claim: pinned, none or malformed, with its file when known. Absent from the pinned PostgreSQL row."
     },
     "checker": {
      "type": "object",
      "required": [
       "id",
       "package",
       "derive_path"
      ],
      "additionalProperties": false,
      "properties": {
       "id": {
        "type": "string"
       },
       "package": {
        "type": [
         "string",
         "null"
        ]
       },
       "derive_path": {
        "type": [
         "string",
         "null"
        ]
       }
      },
      "description": "The checker registry id, package and server derive path. Absent from the pinned PostgreSQL row."
     },
     "docs": {
      "type": "object",
      "required": [
       "quickstart_section"
      ],
      "additionalProperties": false,
      "properties": {
       "quickstart_section": {
        "type": "integer",
        "minimum": 1,
        "maximum": 999
       }
      },
      "description": "The quick-start section named by the profile row, when present. Absent from the pinned PostgreSQL row."
     }
    }
   }
  }
 }
}
