{"openapi":"3.1.0","info":{"title":"ThunderCode Backend API","version":"1.0.0"},"paths":{"/health":{"get":{"responses":{"200":{"description":"Liveness"}}}},"/ready":{"get":{"responses":{"200":{"description":"Ready"},"503":{"description":"Database unavailable"}}}},"/metrics":{"get":{"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Internal Prometheus metrics"},"401":{"description":"Unauthenticated"}}}},"/api/v1/openapi.json":{"get":{"responses":{"200":{"description":"OpenAPI contract"}}}},"/api/v1/connections/github":{"get":{"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tenant public GitHub connections","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["connections"],"properties":{"connections":{"type":"array","items":{"$ref":"#/components/schemas/GithubConnection"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"}}},"post":{"security":[{"bearerAuth":[]}],"description":"Connects a repository. When the authenticated tenant has a recorded GitHub App installation the repository is verified with an installation access token, so private and internal repositories are accepted; otherwise the unauthenticated public client is used and non-public repositories are refused. The installation is always derived from the tenant, never from the request body.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubConnectionCreate"}}}},"responses":{"201":{"description":"Connected repository","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubConnection"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Repository is not public, or not covered by the tenant installation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Repository not found"}}}},"/api/v1/connections/github/private":{"post":{"security":[{"bearerAuth":[]}],"description":"Connects a repository using the tenant GitHub App installation only, with no public fallback. The installation is resolved from the authenticated tenant stored provider connection; a request-supplied installation is rejected. Read-only: no comment, check, status, commit or push is ever issued.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubConnectionCreate"}}}},"responses":{"201":{"description":"Connected repository, private or internal included","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubConnection"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Repository is not covered by the tenant installation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Repository not found"},"409":{"description":"Tenant has no recorded GitHub App installation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub App is not configured"}}}},"/api/v1/connections/github/installation":{"get":{"security":[{"bearerAuth":[]}],"description":"Reports whether the authenticated tenant has a recorded GitHub App installation, so the UI can offer installation versus private-repository connection. Returns non-sensitive account metadata only: no installation id, token, code or secret.","responses":{"200":{"description":"Tenant installation presence","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubInstallationStatus"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"503":{"description":"GitHub App is not configured"}}}},"/api/v1/connections/github/{id}":{"parameters":[{"$ref":"#/components/parameters/GithubConnectionId"}],"get":{"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubConnection"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/connections/github/{id}/pulls":{"get":{"security":[{"bearerAuth":[]}],"description":"Lists open pull requests for a connected repository. Uses the tenant GitHub App installation when one is recorded, so private repositories are supported; the client is pinned to this connection repository.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"maximum":20}}],"responses":{"200":{"description":"Open pull requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubPullPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Repository is not covered by the tenant installation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Invalid GitHub data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/reviews/from-github":{"post":{"security":[{"bearerAuth":[]}],"description":"Creates a deterministic review from a pull request on a connected repository. Uses the tenant GitHub App installation when one is recorded (private repositories included) and the unauthenticated public client otherwise. The pull head SHA is bound to the diff and to the idempotency claim.","parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GithubReviewCreate"}}}},"responses":{"201":{"description":"Review created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Repository is not covered by the tenant installation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Diff rejected"}}}},"/api/v1/reviews":{"get":{"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tenant reviews","content":{"application/json":{"schema":{"type":"object","properties":{"reviews":{"type":"array","items":{"$ref":"#/components/schemas/Review"}}}}}}}}},"post":{"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReview"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"400":{"description":"Invalid review"},"413":{"description":"Review too large"}}}},"/api/v1/reviews/{id}":{"get":{"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"404":{"description":"Not found"}}}},"/api/v1/reviews/{id}/cancel":{"post":{"security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ReviewId"}],"responses":{"200":{"description":"Cancelled review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Invalid transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/reviews/{id}/retry":{"post":{"security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ReviewId"}],"responses":{"200":{"description":"Retried review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Invalid transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/connections/github/install-url":{"post":{"security":[{"bearerAuth":[]}],"description":"Issues a server-generated, single-use, expiring, tenant-bound state value and the GitHub App installation URL to redirect the browser to. `install_url` is the App's own installation page, which is the only URL that can create an installation; it falls back to the OAuth authorize URL when GitHub could not be reached. `authorize_url` only authorizes, and is used to identify an already-installed user.","responses":{"201":{"description":"Install URL issued","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["install_url","authorize_url","state","app_install_url"],"properties":{"install_url":{"type":"string","format":"uri"},"authorize_url":{"type":"string","format":"uri"},"state":{"type":"string"},"app_install_url":{"type":["string","null"],"format":"uri"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"503":{"description":"GitHub App is not configured"}}}},"/api/v1/connections/github/callback":{"get":{"description":"GitHub App installation callback, serving both the App Setup URL and the OAuth authorize redirect. With installation_id (+ setup_action) the installation is recorded directly and its account is resolved with the App JWT — no OAuth exchange. With code the exchange runs and the installation the authorizing user holds is resolved. State is single-use, expiring and tenant-bound in both shapes. A user who authorized the App without installing it is redirected with github=not_installed and nothing is recorded. Every failure branch logs a distinct non-secret reason code. Never returns or logs a token, code, state value or secret; always redirects to an allowlisted app URL.","parameters":[{"name":"code","in":"query","schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"string"}},{"name":"installation_id","in":"query","schema":{"type":"string"}},{"name":"setup_action","in":"query","schema":{"type":"string","enum":["install","update"]}}],"responses":{"302":{"description":"Redirect to the app on success or failure"},"503":{"description":"GitHub App is not configured"}}}},"/api/v1/webhooks/github/app":{"post":{"description":"GitHub App-level webhook endpoint. GitHub Apps expose a single webhook URL; the installation id is resolved from the payload at installation.id, not from the path.","responses":{"202":{"description":"Accepted"},"400":{"description":"Missing delivery id, or installation.id missing/malformed in payload"},"401":{"description":"Invalid signature"},"404":{"description":"Unknown provider connection"},"409":{"description":"Duplicate delivery"},"503":{"description":"Unavailable"}}}},"/api/v1/webhooks/github/{installation}":{"post":{"description":"Legacy explicit-installation webhook path, retained for backward compatibility.","parameters":[{"name":"installation","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"Missing delivery id"},"401":{"description":"Invalid signature"},"404":{"description":"Unknown provider connection"},"409":{"description":"Duplicate delivery"},"503":{"description":"Unavailable"}}}}},"components":{"parameters":{"GithubConnectionId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"ReviewId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}},"responses":{"Unauthenticated":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"GithubConnection":{"type":"object","additionalProperties":false,"required":["id","owner","repository","full_name","display_name","html_url","is_active","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"owner":{"type":"string"},"repository":{"type":"string"},"full_name":{"type":"string"},"display_name":{"type":"string"},"html_url":{"type":"string","format":"uri"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"GithubInstallationStatus":{"type":"object","additionalProperties":false,"required":["installed","installation"],"properties":{"installed":{"type":"boolean"},"installation":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["account_login","account_type"],"properties":{"account_login":{"type":["string","null"]},"account_type":{"type":["string","null"]}}}]}}},"GithubConnectionCreate":{"type":"object","additionalProperties":false,"required":["repository"],"properties":{"repository":{"type":"string","minLength":1,"maxLength":300}}},"GithubPull":{"type":"object","additionalProperties":false,"required":["number","title","url","revision"],"properties":{"number":{"type":"integer","minimum":1},"title":{"type":"string"},"url":{"type":"string","format":"uri"},"revision":{"type":"string","minLength":1,"maxLength":200}}},"GithubPullPage":{"type":"object","additionalProperties":false,"required":["pulls","page","has_more"],"properties":{"pulls":{"type":"array","items":{"$ref":"#/components/schemas/GithubPull"}},"page":{"type":"integer","minimum":1},"has_more":{"type":"boolean"}}},"GithubReviewCreate":{"type":"object","additionalProperties":false,"required":["connection_id","pull_number"],"properties":{"connection_id":{"type":"string","format":"uuid"},"pull_number":{"type":"integer","minimum":1}}},"CreateReview":{"type":"object","additionalProperties":false,"required":["repository","revision","files"],"properties":{"repository":{"type":"string","minLength":1,"maxLength":200},"revision":{"type":"string","minLength":1,"maxLength":200},"files":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["path","content"],"properties":{"path":{"type":"string","maxLength":500},"content":{"type":"string","maxLength":2097152}}}}}},"Review":{"type":"object","required":["protocol_version","id","tenant_id","subject_id","repository","revision","status","attempt","findings","files","capability","created_at","updated_at"],"properties":{"protocol_version":{"const":"1.0"},"id":{"type":"string","format":"uuid"},"tenant_id":{"type":"string"},"subject_id":{"type":"string"},"repository":{"type":"string"},"revision":{"type":"string"},"status":{"type":"string"},"attempt":{"type":"integer"},"findings":{"type":"array"},"files":{"type":"array","maxItems":1000,"items":{"type":"object","additionalProperties":false,"required":["path","content"],"properties":{"path":{"type":"string","minLength":1,"maxLength":500},"content":{"type":"string","maxLength":2097152}}}},"capability":{"type":"object","additionalProperties":false,"required":["deterministic_engine","model_gateway","provenance"],"properties":{"deterministic_engine":{"type":"boolean"},"model_gateway":{"type":"boolean"},"provenance":{"type":"string","enum":["deterministic_engine","model_gateway"]}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","trace_id"]}}}}}}