{"components":{"schemas":{"Asset":{"properties":{"name":{"type":"string"},"size_bytes":{"type":"integer"}},"type":"object"},"BatchError":{"description":"Why one snapshot in the batch could not be evaluated.","properties":{"code":{"description":"Same codes as the single-snapshot endpoint.","type":"string"},"index":{"description":"Position in the submitted `snapshots` array.","type":"integer"},"message":{"type":"string"}},"type":"object"},"BatchRequest":{"description":"Many supplied snapshots in one call. Each row is the same Snapshot shape with the same required fields, and each row gets either a report or an error against its index without stopping the others.","properties":{"snapshots":{"items":{"$ref":"#/components/schemas/Snapshot"},"maxItems":250,"type":"array"}},"required":["snapshots"],"type":"object"},"BatchResponse":{"description":"One report per snapshot that could be evaluated. A snapshot that fails is reported in `errors` against its index and does not stop the others.","properties":{"errors":{"items":{"$ref":"#/components/schemas/BatchError"},"type":"array"},"reports":{"items":{"$ref":"#/components/schemas/ReportResponse"},"type":"array"},"summary":{"$ref":"#/components/schemas/BatchSummary"}},"type":"object"},"BatchSummary":{"description":"A roll-up over the batch, so a caller does not have to walk every report to see the shape of the answer.","properties":{"checked":{"description":"Snapshots that produced a report.","type":"integer"},"failed":{"description":"Snapshots that could not be evaluated.","type":"integer"},"not_shippable":{"type":"integer"},"shippable":{"type":"integer"},"shippable_with_deductions":{"type":"integer"}},"type":"object"},"Category":{"description":"Which area of the submission the finding is about. Stable, and coarser than `rule_id`.","enum":["documentation_missing","documentation_looks_generated","ai_usage_undeclared","source_unavailable","artifact_missing","live_url_issue","media_evidence_missing","design_artifacts_incomplete","repository_hygiene","process_documentation_missing","broken_link","access_barrier","submission_metadata"],"type":"string"},"Coverage":{"description":"Which inputs were actually read. This is the difference between 'there is no readme' and 'nobody looked', and the engine is careful never to report the second as the first.","properties":{"commit_count_checked":{"type":"boolean"},"file_list_checked":{"type":"boolean"},"input_warnings":{"description":"Recoverable problems found while reading the input: a file listed but unreadable, a date that would not parse. The engine carried on, and says so, rather than quietly treating the gap as absence.","items":{"type":"string"},"type":"array"},"not_checkable":{"description":"Things no repository can answer: whether it runs, whether it is AI-generated, whether it was submitted before. Listed so their absence is never mistaken for a pass.","items":{"type":"string"},"type":"array"},"playable_url_checked":{"type":"boolean"},"readme_checked":{"type":"boolean"},"releases_checked":{"type":"boolean"}},"type":"object"},"DocFile":{"properties":{"content":{"type":"string"},"path":{"description":"Where it lives. `docs/Readme.md` is not rendered on the forge front page, and the engine says so.","type":["string","null"]}},"required":["content"],"type":"object"},"Error":{"properties":{"error":{"properties":{"code":{"description":"Stable identifier. Switch on this, not on the message.","type":"string"},"details":{"description":"Extra context. Omitted entirely when there is none.","items":{"type":"string"},"type":"array"},"message":{"description":"Prose for a human. Not stable, not localised.","type":"string"}},"type":"object"}},"type":"object"},"FetchReport":{"description":"What the fetcher actually read, and what it could not. Every count here is evidence about the read, not about the project.","properties":{"cache_hits":{"type":"integer"},"commit_count":{"type":["integer","null"]},"commit_dates":{"description":"Sampled, oldest first. Effort is judged on the shape of a history, which a count cannot express.","items":{"format":"date-time","type":"string"},"type":"array"},"default_branch":{"type":["string","null"]},"empty_readme":{"description":"Fetched, and empty. Distinct from 'there is no readme', which is a defect in the project rather than in the fetch.","type":"boolean"},"files_read":{"type":"integer"},"readme_bytes":{"type":"integer"},"readme_path":{"type":["string","null"]},"releases":{"type":"integer"},"resolved_ref":{"description":"The ref actually read, which may be a tag or a commit rather than the default branch.","type":["string","null"]},"tree_truncated":{"description":"The forge returned only part of the tree. The engine works from what it got and says so here rather than reporting the missing files as absent.","type":"boolean"},"warnings":{"description":"Problems that did not stop the fetch.","items":{"type":"string"},"type":"array"}},"type":"object"},"FileContent":{"properties":{"content":{"description":"null means the file exists but was not read, not that it is empty.","type":["string","null"]},"path":{"type":"string"},"reason":{"type":["string","null"]}},"required":["path"],"type":"object"},"FileEntry":{"properties":{"path":{"type":"string"},"size_bytes":{"type":"integer"}},"required":["path"],"type":"object"},"Finding":{"properties":{"applies_to":{"description":"Which deliveries or platforms this finding is scoped to, when narrower than the whole project.","items":{"type":"string"},"type":"array"},"category":{"$ref":"#/components/schemas/Category"},"confidence":{"description":"0-1. Present only on heuristic rules, such as the generated-text detector.","type":"number"},"detail":{"description":"Why this matters, and what was measured.","type":"string"},"evidence":{"description":"What the engine saw: paths, matched signals, counts. Empty means the judgement cannot be checked from the project state, and that is itself the honest answer.","items":{"type":"string"},"type":"array"},"outcome":{"description":"rejected: a hard ship requirement failed. deduction: points come off. advisory: worth mentioning, never disqualifies. unverified: nobody can settle this from a repository. Omitted when the finding is advisory.","enum":["rejected","deduction","advisory","unverified"]},"remediation":{"description":"What to do about it.","type":"string"},"rule_id":{"description":"Stable identifier, e.g. `docs.missing`. Switch on this.","type":"string"},"severity":{"$ref":"#/components/schemas/Severity"},"title":{"description":"One line, human readable.","type":"string"}},"type":"object"},"HealthResponse":{"properties":{"criteria":{"description":"Checks the engine can emit, across all three tiers.","type":"integer"},"library_version":{"type":"string"},"registry_rules":{"description":"Transcribed ship requirements.","type":"integer"},"ruleset_version":{"description":"The version of the rule set, not of the code. Quote this when reporting a finding.","type":"string"},"status":{"description":"`ok`, or a description of what failed the self-check.","type":"string"},"uptime_secs":{"type":"integer"}},"type":"object"},"Release":{"properties":{"assets":{"items":{"$ref":"#/components/schemas/Asset"},"type":"array"},"body":{"type":["string","null"]},"name":{"type":["string","null"]},"tag":{"type":["string","null"]},"url":{"type":["string","null"]}},"type":"object"},"ReportResponse":{"properties":{"coverage":{"$ref":"#/components/schemas/Coverage"},"findings":{"items":{"$ref":"#/components/schemas/Finding"},"type":"array"},"input_digest":{"type":"integer"},"ruleset_version":{"type":"string"},"summary":{"$ref":"#/components/schemas/Summary"}},"required":["summary","findings"],"type":"object"},"RequirementsResponse":{"properties":{"requirements":{"items":{"type":"object"},"type":"array"},"ruleset_version":{"type":"string"}},"type":"object"},"RulesResponse":{"properties":{"counts":{"type":"object"},"criteria":{"items":{"type":"object"},"type":"array"},"ruleset_version":{"type":"string"}},"type":"object"},"Severity":{"description":"How a finding reads. Decided by hand and cited to the requirement it comes from, not fitted. Separate from `outcome`, which decides whether it costs anything.","enum":["blocker","warning","info"],"type":"string"},"Snapshot":{"description":"The project state, supplied whole by the caller. Use this when you already hold the readme, the file list and the rest. When you only hold a repository URL, send a UrlRequest to /v1/check/url instead and the engine fetches the state itself. `category`, `summary` and `build_hours` are required; the rest is optional.","properties":{"build_hours":{"description":"Required. Self-reported build effort in hours, whatever system tracks it. Without it the reviewer has no way to judge effort, so there is no report.","type":["number","null"]},"category":{"description":"One of the seven project types: `hardware`, `cross_platform_playable`, `windows_playable`, `linux_playable`, `mac_playable`, `web_playable`, `mobile_app`. Anything else is accepted as free text and the engine sorts out the rest, reporting which type it settled on in `summary.project_type`.","type":"string"},"commit_count":{"type":["integer","null"]},"commit_dates":{"description":"Optional, sampled. Effort is judged on the shape of a history, which a count cannot express.","items":{"format":"date-time","type":"string"},"type":"array"},"docs_url":{"type":["string","null"]},"file_contents":{"description":"Optional. Paths and sizes cannot tell firmware from a desktop application; the contents can.","items":{"$ref":"#/components/schemas/FileContent"},"type":"array"},"files":{"items":{"$ref":"#/components/schemas/FileEntry"},"type":"array"},"playable_url":{"description":"Where someone can actually use it.","type":["string","null"]},"readme":{"$ref":"#/components/schemas/DocFile"},"releases":{"items":{"$ref":"#/components/schemas/Release"},"type":"array"},"repository_url":{"description":"Where the code lives.","type":["string","null"]},"summary":{"description":"Required. The project description in the submitter's own words. An empty string counts as missing.","type":["string","null"]}},"required":["category","summary","build_hours"],"type":"object"},"Summary":{"description":"Two scores that are never combined, because they answer different questions. `readiness_score` moves with `gate_failures`; `quality_score` moves with `deductions`; neither is derived from the other.","properties":{"blockers":{"description":"Findings at blocker severity, across both tiers. Not the same count as `gate_failures`: a finding's severity and its tier are decided separately.","type":"integer"},"deductions":{"description":"Failed quality requirements. These cost points and never disqualify.","type":"integer"},"gate_failures":{"description":"Failed hard ship requirements. Any of these means not shippable as it stands.","type":"integer"},"grade":{"description":"A coarse band over `quality_score`, for reading at a glance.","type":"string"},"info":{"description":"Findings at info severity, across both tiers. An advisory lands here.","type":"integer"},"project_type":{"description":"Which of the seven this was sorted into.","type":"string"},"project_type_inferred":{"description":"True when the category string did not name one of the seven and the file tree decided. Every inference is marked, so a wrong guess is visible rather than silently authoritative.","type":"boolean"},"quality_score":{"description":"0-100, driven by deductions. Independent of the ship decision.","type":"integer"},"readiness_score":{"description":"0-100. An estimate, not a probability of acceptance.","type":"integer"},"unverified":{"description":"Ship requirements no repository can answer. This count is what separates 'nothing failed' from 'everything passed', and it is reported on every response.","type":"integer"},"verdict":{"$ref":"#/components/schemas/Verdict"},"warnings":{"description":"Findings at warning severity, across both tiers.","type":"integer"}},"type":"object"},"UrlReportResponse":{"description":"What was fetched, next to what was made of it. The two are kept apart so a caller can tell a thin repository from a failed fetch.","properties":{"fetch":{"$ref":"#/components/schemas/FetchReport"},"report":{"$ref":"#/components/schemas/ReportResponse"}},"type":"object"},"UrlRequest":{"description":"The URL-first input. Send this when you hold a repository URL and want the engine to fetch the tree, the readme and the releases itself. `repository_url`, `category`, `summary` and `build_hours` are required. Anything else supplied here is used as-is and skips that part of the fetch.","properties":{"build_hours":{"description":"Required. Self-reported build effort in hours. An absent or negative value is a missing field.","type":["number","null"]},"category":{"description":"Required. One of the seven project types: `hardware`, `cross_platform_playable`, `windows_playable`, `linux_playable`, `mac_playable`, `web_playable`, `mobile_app`. Supplying it saves the engine from guessing.","type":["string","null"]},"commit_dates":{"items":{"format":"date-time","type":"string"},"type":"array"},"docs_url":{"type":["string","null"]},"playable_url":{"type":["string","null"]},"repository_url":{"description":"The forge URL. The engine fetches the tree, the readme and the releases itself.","type":"string"},"summary":{"description":"Required. The project description in the submitter's own words. An empty string counts as missing.","type":["string","null"]}},"required":["repository_url","category","summary","build_hours"],"type":"object"},"Verdict":{"description":"The ship decision.","enum":["not_shippable","needs_human_check","shippable_with_deductions","shippable"],"type":"string","x-descriptions":{"needs_human_check":"Nothing checkable failed, but something could not be verified. Not yet a clean bill of health.","not_shippable":"A hard ship requirement failed.","shippable":"Every ship requirement is met and nothing measurable is missing.","shippable_with_deductions":"Every checkable requirement is met; deductions are outstanding and will cost points."}},"VersionResponse":{"properties":{"corpus":{"description":"What the thresholds were read off, and how far back it goes.","type":"string"},"library_version":{"type":"string"},"notes":{"type":"string"},"ruleset_version":{"type":"string"}},"type":"object"}}},"info":{"description":"A rules engine that reports what is likely to get a project submission turned down.\n\nEvery finding carries the fix and, where the judgement can be checked from the\nproject state, the evidence behind it. Severity is decided by hand and cited to\nthe requirement it comes from. Nothing here is fitted to a corpus, and an advisory\ncan never reject a project.\n\n## Two scores, never combined\n\n`readiness_score` is driven by gate failures and `quality_score` by deductions.\nThey answer different questions, so they are never averaged: a project that\nships with fifteen deductions is a different situation from one that fails a hard\nrequirement.\n\n## A clean verdict is not an acceptance\n\nTwo central requirements, that the project works and that anyone with minimal\ntechnical knowledge can experience it, need somebody to run it. They are\nreported as `unverified`, counted in `summary.unverified`, and cap the verdict at\n`needs_human_check` no matter how good the repository looks.\n\n## Where to start\n\n- `POST /v1/check` if you already have a project state.\n- `POST /v1/check/url` if you only have a repository URL.\n- `/v1/requirements` returns the transcribed guide with a citation on every\n  requirement, which is where you check whether a particular finding is fair.","license":{"name":"MIT"},"summary":"What is likely to get this project turned down, and how to fix it.","title":"preflight","version":"2026.10.02"},"openapi":"3.1.0","paths":{"/":{"get":{"operationId":"ui","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Success."}},"summary":"A web form for the same check.","tags":["service"]}},"/docs":{"get":{"operationId":"docs","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Success."}},"summary":"Human-readable reference, rendered from this document.","tags":["service"]}},"/healthz":{"get":{"operationId":"healthz","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}},"description":"Success."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed its own self-check. Nothing the caller sent can cause this."}},"summary":"Liveness, versions and rule counts.","tags":["service"]}},"/v1/check":{"post":{"operationId":"check","requestBody":{"content":{"application/json":{"example":{"build_hours":42.0,"category":"web playable","readme":{"content":"# your-project\n\nOne paragraph saying what it is and who it is for, then how to run it.\n\n![the app running](screenshot.png)\n","path":"README.md"},"repository_url":"https://github.com/you/your-project","summary":"A dashboard for watching a service, written for people who want a status page without running one."},"schema":{"$ref":"#/components/schemas/Snapshot"}}},"description":"The project state to evaluate.","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportResponse"}}},"description":"Success."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body could not be parsed as JSON, or a field had the wrong type."},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body exceeded 32 MB."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body parsed, but there is nothing in it to evaluate. A snapshot needs a readme, a file list, or a repository URL. Deliberately not a 400: the request was well-formed and simply says nothing, and the fix is to supply content rather than to fix the syntax."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed. Nothing the caller sent can cause this."}},"summary":"Evaluate a snapshot you supply. Makes no network calls. For testing and specialised use; send a repository URL to /v1/check/url instead.","tags":["check"]}},"/v1/check/batch":{"post":{"operationId":"check_batch","requestBody":{"content":{"application/json":{"example":{"snapshots":[{"build_hours":10.0,"category":"web playable","repository_url":"https://github.com/you/one","summary":"First project."},{"build_hours":20.0,"category":"hardware","repository_url":"https://github.com/you/two","summary":"Second project."}]},"schema":{"$ref":"#/components/schemas/BatchRequest"}}},"description":"Up to 250 snapshots.","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"}}},"description":"Success."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body could not be parsed as JSON, or a field had the wrong type."},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body exceeded 32 MB."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body parsed, but there is nothing in it to evaluate. A snapshot needs a readme, a file list, or a repository URL. Deliberately not a 400: the request was well-formed and simply says nothing, and the fix is to supply content rather than to fix the syntax."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed. Nothing the caller sent can cause this."}},"summary":"Evaluate many supplied snapshots at once. Maximum 250. For testing and specialised use.","tags":["check"]}},"/v1/check/url":{"post":{"operationId":"check_url","requestBody":{"content":{"application/json":{"example":{"build_hours":42.0,"category":"web playable","repository_url":"https://github.com/you/your-project","summary":"A dashboard for watching a service, written for people who want a status page without running one."},"schema":{"$ref":"#/components/schemas/UrlRequest"}}},"description":"Where the repository is.","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlReportResponse"}}},"description":"Success."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body could not be parsed as JSON, or a field had the wrong type."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The repository does not exist, is private, or has moved."},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body exceeded 32 MB."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body parsed, but there is nothing in it to evaluate. A snapshot needs a readme, a file list, or a repository URL. Deliberately not a 400: the request was well-formed and simply says nothing, and the fix is to supply content rather than to fix the syntax."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The forge's rate limit was hit. Set FORGE_TOKEN."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed. Nothing the caller sent can cause this."},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The repository could not be fetched, or the forge returned an error."},"504":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The repository could not be fetched in time."}},"summary":"The main endpoint. Fetch a repository by URL, then evaluate it.","tags":["check"]}},"/v1/openapi.json":{"get":{"operationId":"openapi","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Success."}},"summary":"This document.","tags":["service"]}},"/v1/requirements":{"get":{"operationId":"requirements","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequirementsResponse"}}},"description":"Success."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed its own self-check. Nothing the caller sent can cause this."}},"summary":"The transcribed ship guide: requirements, citations, tiers and merges.","tags":["reference"]}},"/v1/rules":{"get":{"operationId":"rules","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RulesResponse"}}},"description":"Success."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed its own self-check. Nothing the caller sent can cause this."}},"summary":"Every criterion the engine can emit, and what it cannot check.","tags":["reference"]}},"/v1/version":{"get":{"operationId":"version","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionResponse"}}},"description":"Success."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The engine failed its own self-check. Nothing the caller sent can cause this."}},"summary":"Build information, including what the rules were calibrated against.","tags":["service"]}}},"servers":[{"description":"This instance","url":"/"}],"tags":[{"description":"Evaluate a project. POST /v1/check/url is the way in: give it a repository URL and it fetches the rest. POST /v1/check and /v1/check/batch take snapshots you supply yourself, for testing, debugging and specialised use.","name":"check"},{"description":"Health, version, and the contract itself.","name":"service"},{"description":"The rule set and the transcribed ship guide.","name":"reference"}]}