{
	"openapi": "3.1.0",
	"info": {
		"title": "Rustine API",
		"version": "2026-10-13",
		"summary": "Scan your sites, read the issues, unlock their fixes, from your own scripts.",
		"description": "The REST API of Rustine, the security scanner: the same capabilities as the MCP server, with the same rules.\n\n- **Authentication**: `Authorization: Bearer rst_…`, an API key created in the app (Settings › API keys), or an OAuth access token (`rat_…`) of the MCP server with the right scope. Never in the URL.\n- **Plan**: the API answers a workspace on Pro, or on Solo during its 14-day trial (started by the first API or MCP call); otherwise `402 api_plan_required`.\n- **Ownership**: Rustine only scans the sites of your projects and only sends read-only requests; full audits and issue details need a site verified in the app.\n- **Rate limit**: 120 requests per minute per key (`X-RateLimit-*` headers, `Retry-After` on 429).\n- **Idempotency**: send `Idempotency-Key` on POST to retry safely; the first response is replayed for 24 hours.\n- **Pagination**: `limit` (1 to 100, default 20) and `starting_after` (the last id of the previous page); lists answer `{ object: \"list\", data, has_more }`.\n- **Errors**: `{ error: { type, code, message, doc_url } }`, with `message` in the language of the key's owner.\n- **Versioning**: every response carries `Rustine-Version: 2026-10-13`; breaking changes get a new version.",
		"contact": {
			"name": "Rustine",
			"url": "https://rustine.io",
			"email": "contact@rustine.io"
		}
	},
	"servers": [
		{
			"url": "https://api.rustine.io/v1",
			"description": "Production"
		}
	],
	"security": [
		{
			"bearerAuth": []
		}
	],
	"tags": [
		{
			"name": "Account",
			"description": "The workspace of the key: plan and quotas."
		},
		{
			"name": "Projects",
			"description": "Projects and their targets (sites, repositories)."
		},
		{
			"name": "Scans",
			"description": "Start scans and read their results."
		},
		{
			"name": "Issues",
			"description": "Issues found, their fixes, rechecks and statuses."
		}
	],
	"paths": {
		"/account": {
			"get": {
				"operationId": "getAccount",
				"tags": [
					"Account"
				],
				"summary": "Retrieve the account",
				"description": "The workspace of the key: its plan, whether the API answers (Pro, or Solo during its 14-day trial), the quotas left this period, pack credits and projects. Free: read it before a batch of scans to know what is left.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [],
				"responses": {
					"200": {
						"description": "The account.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Account"
								},
								"example": {
									"object": "account",
									"workspace": {
										"id": "0b6f4a52-3c1e-4d7a-9f2e-5a8c1d3e7b90",
										"name": "example.com"
									},
									"plan": "pro",
									"api_access": {
										"allowed": true,
										"trial_ends_at": null
									},
									"period": {
										"start": "2026-10-01T00:00:00.000Z",
										"end": "2026-10-31T00:00:00.000Z"
									},
									"quotas": {
										"quick_scans": {
											"limit": 300,
											"used": 12,
											"remaining": 288
										},
										"audits": {
											"limit": 180,
											"used": 4,
											"remaining": 176
										},
										"fixes": {
											"limit": 90,
											"used": 7,
											"remaining": 83
										}
									},
									"free_fixes_left": 0,
									"pack_credits": {
										"scans": 0,
										"fixes": 10
									},
									"projects": {
										"limit": 3,
										"used": 2,
										"paused": 0
									},
									"addresses_per_project": 20,
									"features": {
										"full_audit": true,
										"repositories": true,
										"ai_fix": true,
										"alerts": true
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		},
		"/projects": {
			"get": {
				"operationId": "listProjects",
				"tags": [
					"Projects"
				],
				"summary": "List projects",
				"description": "The workspace's projects, oldest first, each with its targets (sites and repositories), score, grade and open issues.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"description": "How many objects to return, 1 to 100 (default 20).",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					},
					{
						"name": "starting_after",
						"in": "query",
						"required": false,
						"description": "Cursor: the id of the last object of the previous page.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of projects.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"object": {
											"type": "string",
											"enum": [
												"list"
											]
										},
										"data": {
											"type": "array",
											"items": {
												"$ref": "#/components/schemas/Project"
											}
										},
										"has_more": {
											"type": "boolean",
											"description": "Whether more objects follow: pass the last id as `starting_after`."
										}
									},
									"required": [
										"object",
										"data",
										"has_more"
									],
									"additionalProperties": false
								},
								"example": {
									"object": "list",
									"data": [
										{
											"id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
											"object": "project",
											"name": "Example SaaS",
											"score": 62,
											"grade": "C",
											"open_issues": 3,
											"paused": false,
											"last_scan_at": "2026-10-13T08:42:10.000Z",
											"targets": [
												{
													"id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
													"object": "target",
													"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
													"kind": "domain",
													"address": "app.example.com",
													"verified": true,
													"score": 62,
													"grade": "C",
													"last_scan_at": "2026-10-13T08:42:10.000Z",
													"hidden_issues": null
												},
												{
													"id": "c1a3e5f7-2b4d-4f6a-8c0e-1a3b5c7d9e2f",
													"object": "target",
													"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
													"kind": "repository",
													"address": "example/saas",
													"verified": true,
													"score": 81,
													"grade": "B",
													"last_scan_at": "2026-10-12T17:03:55.000Z",
													"hidden_issues": null
												}
											]
										}
									],
									"has_more": false
								}
							}
						}
					},
					"400": {
						"description": "Error: `parameter_invalid`, `cursor_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"cursor_invalid": {
										"$ref": "#/components/examples/cursor_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			},
			"post": {
				"operationId": "createProject",
				"tags": [
					"Projects"
				],
				"summary": "Create a project",
				"description": "Creates a project, optionally with its first site. Counts against the plan's projects. Only add a site you own or are allowed to test: until it is verified in the app, Rustine only runs quick scans on it and hides the details of its issues. On the Free plan, a domain another free account follows is refused.",
				"x-oauth-scope": "scans:run",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"$ref": "#/components/parameters/IdempotencyKey"
					}
				],
				"responses": {
					"201": {
						"description": "The project created.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Idempotent-Replayed": {
								"$ref": "#/components/headers/IdempotentReplayed"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Project"
								},
								"example": {
									"id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
									"object": "project",
									"name": "Example SaaS",
									"score": null,
									"grade": null,
									"open_issues": 0,
									"paused": false,
									"last_scan_at": null,
									"targets": [
										{
											"id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
											"object": "target",
											"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
											"kind": "domain",
											"address": "app.example.com",
											"verified": false,
											"score": null,
											"grade": null,
											"last_scan_at": null,
											"hidden_issues": null
										}
									]
								}
							}
						}
					},
					"400": {
						"description": "Error: `body_invalid`, `parameter_invalid`, `url_invalid`, `url_private`, `idempotency_key_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"body_invalid": {
										"$ref": "#/components/examples/body_invalid"
									},
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"url_invalid": {
										"$ref": "#/components/examples/url_invalid"
									},
									"url_private": {
										"$ref": "#/components/examples/url_private"
									},
									"idempotency_key_invalid": {
										"$ref": "#/components/examples/idempotency_key_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`, `project_limit_reached`, `domain_taken`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									},
									"project_limit_reached": {
										"$ref": "#/components/examples/project_limit_reached"
									},
									"domain_taken": {
										"$ref": "#/components/examples/domain_taken"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"409": {
						"description": "Error: `site_excluded`, `idempotency_conflict`, `idempotency_in_progress`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"site_excluded": {
										"$ref": "#/components/examples/site_excluded"
									},
									"idempotency_conflict": {
										"$ref": "#/components/examples/idempotency_conflict"
									},
									"idempotency_in_progress": {
										"$ref": "#/components/examples/idempotency_in_progress"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				},
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 80,
										"description": "Project name."
									},
									"url": {
										"type": "string",
										"maxLength": 2048,
										"description": "The project's first site (optional). Only add a site you own or are allowed to test."
									}
								},
								"required": [
									"name"
								],
								"additionalProperties": false
							},
							"example": {
								"name": "Example SaaS",
								"url": "https://app.example.com"
							}
						}
					}
				}
			}
		},
		"/projects/{project_id}": {
			"get": {
				"operationId": "getProject",
				"tags": [
					"Projects"
				],
				"summary": "Retrieve a project",
				"description": "One project with its targets.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "project_id",
						"in": "path",
						"required": true,
						"description": "Project id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The project.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Project"
								},
								"example": {
									"id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
									"object": "project",
									"name": "Example SaaS",
									"score": 62,
									"grade": "C",
									"open_issues": 3,
									"paused": false,
									"last_scan_at": "2026-10-13T08:42:10.000Z",
									"targets": [
										{
											"id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
											"object": "target",
											"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
											"kind": "domain",
											"address": "app.example.com",
											"verified": true,
											"score": 62,
											"grade": "C",
											"last_scan_at": "2026-10-13T08:42:10.000Z",
											"hidden_issues": null
										},
										{
											"id": "c1a3e5f7-2b4d-4f6a-8c0e-1a3b5c7d9e2f",
											"object": "target",
											"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
											"kind": "repository",
											"address": "example/saas",
											"verified": true,
											"score": 81,
											"grade": "B",
											"last_scan_at": "2026-10-12T17:03:55.000Z",
											"hidden_issues": null
										}
									]
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		},
		"/projects/{project_id}/targets": {
			"post": {
				"operationId": "createTarget",
				"tags": [
					"Projects"
				],
				"summary": "Add a site to a project",
				"description": "Adds a site (an address) to a project, within the plan's addresses per project. Only add a site you own or are allowed to test; prove it is yours in the app to unlock full audits and issue details. GitHub repositories are connected from the app.",
				"x-oauth-scope": "scans:run",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "project_id",
						"in": "path",
						"required": true,
						"description": "Project id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"$ref": "#/components/parameters/IdempotencyKey"
					}
				],
				"responses": {
					"201": {
						"description": "The target created.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Idempotent-Replayed": {
								"$ref": "#/components/headers/IdempotentReplayed"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Target"
								},
								"example": {
									"id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
									"object": "target",
									"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
									"kind": "domain",
									"address": "api.example.com",
									"verified": false,
									"score": null,
									"grade": null,
									"last_scan_at": null,
									"hidden_issues": null
								}
							}
						}
					},
					"400": {
						"description": "Error: `body_invalid`, `parameter_invalid`, `url_invalid`, `url_private`, `idempotency_key_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"body_invalid": {
										"$ref": "#/components/examples/body_invalid"
									},
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"url_invalid": {
										"$ref": "#/components/examples/url_invalid"
									},
									"url_private": {
										"$ref": "#/components/examples/url_private"
									},
									"idempotency_key_invalid": {
										"$ref": "#/components/examples/idempotency_key_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`, `address_limit_reached`, `domain_taken`, `project_paused`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									},
									"address_limit_reached": {
										"$ref": "#/components/examples/address_limit_reached"
									},
									"domain_taken": {
										"$ref": "#/components/examples/domain_taken"
									},
									"project_paused": {
										"$ref": "#/components/examples/project_paused"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"409": {
						"description": "Error: `site_excluded`, `target_exists`, `idempotency_conflict`, `idempotency_in_progress`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"site_excluded": {
										"$ref": "#/components/examples/site_excluded"
									},
									"target_exists": {
										"$ref": "#/components/examples/target_exists"
									},
									"idempotency_conflict": {
										"$ref": "#/components/examples/idempotency_conflict"
									},
									"idempotency_in_progress": {
										"$ref": "#/components/examples/idempotency_in_progress"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				},
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"url": {
										"type": "string",
										"minLength": 3,
										"maxLength": 2048,
										"description": "Address of the site (`https://app.example.com`). Only a site you own or are allowed to test."
									}
								},
								"required": [
									"url"
								],
								"additionalProperties": false
							},
							"example": {
								"url": "https://api.example.com"
							}
						}
					}
				}
			}
		},
		"/targets/{target_id}/scans": {
			"post": {
				"operationId": "createScan",
				"tags": [
					"Scans"
				],
				"summary": "Start a scan",
				"description": "Starts a scan of a target and returns at once (201, status `queued`): poll `GET /scans/{scan_id}` until `done` or `failed` (usually under a minute). `quick` runs what any visitor could see; `audit` adds deeper read-only checks and needs a verified site and Solo or Pro; a repository is analysed (`repository`). A scan uses the period's quota, a repository analysis does not. If a scan of the target is running, or one of the same kind finished within the hour, that scan is returned instead (200, `reused: true`) and nothing is counted. If the site's proof no longer holds when an audit starts, a quick scan runs instead (`audit_fallback`) and no audit is counted. Rustine only sends read-only requests.",
				"x-oauth-scope": "scans:run",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "target_id",
						"in": "path",
						"required": true,
						"description": "Target id (a site or a repository of a project).",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"$ref": "#/components/parameters/IdempotencyKey"
					}
				],
				"responses": {
					"200": {
						"description": "An existing scan, returned instead of a new one (`reused: true`).",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Idempotent-Replayed": {
								"$ref": "#/components/headers/IdempotentReplayed"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Scan"
								},
								"example": {
									"id": "e7f9a1b3-5c7d-4e9f-b1a3-c5d7e9f1a3b5",
									"object": "scan",
									"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
									"kind": "audit",
									"status": "done",
									"score": 62,
									"grade": "C",
									"coverage": 0.94,
									"audit_fallback": false,
									"reused": true,
									"next_real_scan_at": "2026-10-13T09:41:02.000Z",
									"created_at": "2026-10-13T08:41:02.000Z",
									"finished_at": "2026-10-13T08:42:10.000Z"
								}
							}
						}
					},
					"201": {
						"description": "The scan started.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Idempotent-Replayed": {
								"$ref": "#/components/headers/IdempotentReplayed"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Scan"
								},
								"example": {
									"id": "e7f9a1b3-5c7d-4e9f-b1a3-c5d7e9f1a3b5",
									"object": "scan",
									"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
									"kind": "audit",
									"status": "queued",
									"score": null,
									"grade": null,
									"coverage": null,
									"audit_fallback": false,
									"reused": false,
									"next_real_scan_at": null,
									"created_at": "2026-10-13T08:41:02.000Z",
									"finished_at": null
								}
							}
						}
					},
					"400": {
						"description": "Error: `body_invalid`, `parameter_invalid`, `target_kind_invalid`, `idempotency_key_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"body_invalid": {
										"$ref": "#/components/examples/body_invalid"
									},
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"target_kind_invalid": {
										"$ref": "#/components/examples/target_kind_invalid"
									},
									"idempotency_key_invalid": {
										"$ref": "#/components/examples/idempotency_key_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`, `audit_not_in_plan`, `repositories_not_in_plan`, `scan_quota_exceeded`, `audit_quota_exceeded`, `project_paused`, `domain_taken`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									},
									"audit_not_in_plan": {
										"$ref": "#/components/examples/audit_not_in_plan"
									},
									"repositories_not_in_plan": {
										"$ref": "#/components/examples/repositories_not_in_plan"
									},
									"scan_quota_exceeded": {
										"$ref": "#/components/examples/scan_quota_exceeded"
									},
									"audit_quota_exceeded": {
										"$ref": "#/components/examples/audit_quota_exceeded"
									},
									"project_paused": {
										"$ref": "#/components/examples/project_paused"
									},
									"domain_taken": {
										"$ref": "#/components/examples/domain_taken"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"409": {
						"description": "Error: `proof_required`, `site_excluded`, `github_unavailable`, `idempotency_conflict`, `idempotency_in_progress`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"proof_required": {
										"$ref": "#/components/examples/proof_required"
									},
									"site_excluded": {
										"$ref": "#/components/examples/site_excluded"
									},
									"github_unavailable": {
										"$ref": "#/components/examples/github_unavailable"
									},
									"idempotency_conflict": {
										"$ref": "#/components/examples/idempotency_conflict"
									},
									"idempotency_in_progress": {
										"$ref": "#/components/examples/idempotency_in_progress"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`, `repository_analyses_limit_reached`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									},
									"repository_analyses_limit_reached": {
										"$ref": "#/components/examples/repository_analyses_limit_reached"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`, `platform_busy`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									},
									"platform_busy": {
										"$ref": "#/components/examples/platform_busy"
									}
								}
							}
						}
					}
				},
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"kind": {
										"enum": [
											"quick",
											"audit",
											"repository"
										],
										"description": "`quick` (default for a site), `audit` (verified sites, Solo and Pro) or `repository` (the only kind, and the default, for a repository).",
										"type": "string"
									}
								},
								"required": [],
								"additionalProperties": false
							},
							"example": {
								"kind": "audit"
							}
						}
					}
				}
			}
		},
		"/scans/{scan_id}": {
			"get": {
				"operationId": "getScan",
				"tags": [
					"Scans"
				],
				"summary": "Retrieve a scan",
				"description": "A scan and its result once `done`: score, grade and coverage. Its issues are read with `GET /projects/{project_id}/issues`.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "scan_id",
						"in": "path",
						"required": true,
						"description": "Scan id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The scan.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Scan"
								},
								"example": {
									"id": "e7f9a1b3-5c7d-4e9f-b1a3-c5d7e9f1a3b5",
									"object": "scan",
									"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
									"kind": "audit",
									"status": "done",
									"score": 62,
									"grade": "C",
									"coverage": 0.94,
									"audit_fallback": false,
									"reused": false,
									"next_real_scan_at": null,
									"created_at": "2026-10-13T08:41:02.000Z",
									"finished_at": "2026-10-13T08:42:10.000Z"
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		},
		"/projects/{project_id}/scans": {
			"get": {
				"operationId": "listProjectScans",
				"tags": [
					"Scans"
				],
				"summary": "List a project's scans",
				"description": "The scans of a project's targets, newest first, within the history the plan shows (30 days on Free, a year on Solo and Pro).",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "project_id",
						"in": "path",
						"required": true,
						"description": "Project id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"description": "How many objects to return, 1 to 100 (default 20).",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					},
					{
						"name": "starting_after",
						"in": "query",
						"required": false,
						"description": "Cursor: the id of the last object of the previous page.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of scans.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"object": {
											"type": "string",
											"enum": [
												"list"
											]
										},
										"data": {
											"type": "array",
											"items": {
												"$ref": "#/components/schemas/Scan"
											}
										},
										"has_more": {
											"type": "boolean",
											"description": "Whether more objects follow: pass the last id as `starting_after`."
										}
									},
									"required": [
										"object",
										"data",
										"has_more"
									],
									"additionalProperties": false
								},
								"example": {
									"object": "list",
									"data": [
										{
											"id": "e7f9a1b3-5c7d-4e9f-b1a3-c5d7e9f1a3b5",
											"object": "scan",
											"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
											"kind": "audit",
											"status": "done",
											"score": 62,
											"grade": "C",
											"coverage": 0.94,
											"audit_fallback": false,
											"reused": false,
											"next_real_scan_at": null,
											"created_at": "2026-10-13T08:41:02.000Z",
											"finished_at": "2026-10-13T08:42:10.000Z"
										}
									],
									"has_more": true
								}
							}
						}
					},
					"400": {
						"description": "Error: `parameter_invalid`, `cursor_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"cursor_invalid": {
										"$ref": "#/components/examples/cursor_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		},
		"/projects/{project_id}/issues": {
			"get": {
				"operationId": "listProjectIssues",
				"tags": [
					"Issues"
				],
				"summary": "List a project's issues",
				"description": "The issues of a project, worst first (open ones first, then by severity). Texts are in the language of the key's owner. Issues of a site that is not verified yet are never listed: the target's `hidden_issues` gives their counts until the site is proved in the app.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "project_id",
						"in": "path",
						"required": true,
						"description": "Project id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"description": "How many objects to return, 1 to 100 (default 20).",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					},
					{
						"name": "starting_after",
						"in": "query",
						"required": false,
						"description": "Cursor: the id of the last object of the previous page.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"name": "status",
						"in": "query",
						"required": false,
						"description": "Only issues in this status.",
						"schema": {
							"enum": [
								"open",
								"fixed",
								"false_positive",
								"accepted_risk"
							],
							"type": "string"
						}
					},
					{
						"name": "severity",
						"in": "query",
						"required": false,
						"description": "Only issues of this severity.",
						"schema": {
							"enum": [
								"critical",
								"high",
								"medium",
								"low",
								"info"
							],
							"type": "string"
						}
					},
					{
						"name": "target_id",
						"in": "query",
						"required": false,
						"description": "Only the issues of this target.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of issues.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"object": {
											"type": "string",
											"enum": [
												"list"
											]
										},
										"data": {
											"type": "array",
											"items": {
												"$ref": "#/components/schemas/Issue"
											}
										},
										"has_more": {
											"type": "boolean",
											"description": "Whether more objects follow: pass the last id as `starting_after`."
										}
									},
									"required": [
										"object",
										"data",
										"has_more"
									],
									"additionalProperties": false
								},
								"example": {
									"object": "list",
									"data": [
										{
											"id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
											"object": "issue",
											"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
											"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
											"address": "app.example.com",
											"control_id": "exposed-env-file",
											"family": "exposedFiles",
											"severity": "high",
											"confidence": "confirmed",
											"status": "open",
											"title": "Your .env file is readable online.",
											"explanation": "Your secrets are served to everyone.",
											"detail": "GET /.env → 200",
											"location": "/.env",
											"file": null,
											"line": null,
											"commit": null,
											"evidence": "GET /.env → 200 (STRIPE_SECRET_KEY=sk_live_••••)",
											"fix_unlocked": false,
											"status_reason": null,
											"marked_by": null,
											"first_seen_at": "2026-10-13T08:42:10.000Z",
											"last_seen_at": "2026-10-13T08:42:10.000Z",
											"fixed_at": null
										}
									],
									"has_more": false
								}
							}
						}
					},
					"400": {
						"description": "Error: `parameter_invalid`, `cursor_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"cursor_invalid": {
										"$ref": "#/components/examples/cursor_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		},
		"/issues/{issue_id}": {
			"get": {
				"operationId": "getIssue",
				"tags": [
					"Issues"
				],
				"summary": "Retrieve an issue",
				"description": "One issue, with its explanation, location and masked evidence.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "issue_id",
						"in": "path",
						"required": true,
						"description": "Issue id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The issue.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Issue"
								},
								"example": {
									"id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
									"object": "issue",
									"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
									"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
									"address": "app.example.com",
									"control_id": "exposed-env-file",
									"family": "exposedFiles",
									"severity": "high",
									"confidence": "confirmed",
									"status": "open",
									"title": "Your .env file is readable online.",
									"explanation": "Your secrets are served to everyone.",
									"detail": "GET /.env → 200",
									"location": "/.env",
									"file": null,
									"line": null,
									"commit": null,
									"evidence": "GET /.env → 200 (STRIPE_SECRET_KEY=sk_live_••••)",
									"fix_unlocked": false,
									"status_reason": null,
									"marked_by": null,
									"first_seen_at": "2026-10-13T08:42:10.000Z",
									"last_seen_at": "2026-10-13T08:42:10.000Z",
									"fixed_at": null
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			},
			"patch": {
				"operationId": "updateIssue",
				"tags": [
					"Issues"
				],
				"summary": "Mark an issue",
				"description": "Marks an issue as a false positive (it leaves the score) or an accepted risk (it stays in the score), or opens it again. The reason is required for the first two and shown in the app with the name of the key. Only mark a false positive after checking that the reported value is fake or unreachable; to report a fix, call recheck instead.",
				"x-oauth-scope": "issues:write",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "issue_id",
						"in": "path",
						"required": true,
						"description": "Issue id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The issue updated.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Issue"
								},
								"example": {
									"id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
									"object": "issue",
									"project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
									"target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
									"address": "app.example.com",
									"control_id": "exposed-env-file",
									"family": "exposedFiles",
									"severity": "high",
									"confidence": "confirmed",
									"status": "false_positive",
									"title": "Your .env file is readable online.",
									"explanation": "Your secrets are served to everyone.",
									"detail": "GET /.env → 200",
									"location": "/.env",
									"file": null,
									"line": null,
									"commit": null,
									"evidence": "GET /.env → 200 (STRIPE_SECRET_KEY=sk_live_••••)",
									"fix_unlocked": false,
									"status_reason": "README.md:42 shows a placeholder key (sk_test_xxxx) that opens nothing.",
									"marked_by": "CI script",
									"first_seen_at": "2026-10-13T08:42:10.000Z",
									"last_seen_at": "2026-10-13T08:42:10.000Z",
									"fixed_at": null
								}
							}
						}
					},
					"400": {
						"description": "Error: `body_invalid`, `parameter_invalid`, `reason_required`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"body_invalid": {
										"$ref": "#/components/examples/body_invalid"
									},
									"parameter_invalid": {
										"$ref": "#/components/examples/parameter_invalid"
									},
									"reason_required": {
										"$ref": "#/components/examples/reason_required"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"409": {
						"description": "Error: `issue_already_fixed`, `proof_required`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"issue_already_fixed": {
										"$ref": "#/components/examples/issue_already_fixed"
									},
									"proof_required": {
										"$ref": "#/components/examples/proof_required"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				},
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"status": {
										"enum": [
											"open",
											"false_positive",
											"accepted_risk"
										],
										"description": "`false_positive` (leaves the score), `accepted_risk` (stays in the score) or `open` to undo.",
										"type": "string"
									},
									"reason": {
										"type": "string",
										"minLength": 10,
										"maxLength": 500,
										"description": "Why, with the evidence checked (10 to 500 characters). Required for `false_positive` and `accepted_risk`; shown in the app with the key's name."
									}
								},
								"required": [
									"status"
								],
								"additionalProperties": false
							},
							"example": {
								"status": "false_positive",
								"reason": "README.md:42 shows a placeholder key (sk_test_xxxx) that opens nothing."
							}
						}
					}
				}
			}
		},
		"/issues/{issue_id}/fix": {
			"post": {
				"operationId": "unlockFix",
				"tags": [
					"Issues"
				],
				"summary": "Unlock the fix of an issue",
				"description": "Unlocks the fix of an issue and returns it: instructions for a coding agent, to apply in the codebase (Rustine never edits code). COST: the first unlock of an issue consumes one fix from the period's quota (then a pack credit); calling it again for the same issue is free (`charged: false`). Issues of an unverified site cannot be unlocked. After deploying the fix, call recheck.",
				"x-oauth-scope": "fixes:unlock",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "issue_id",
						"in": "path",
						"required": true,
						"description": "Issue id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"$ref": "#/components/parameters/IdempotencyKey"
					}
				],
				"responses": {
					"200": {
						"description": "The fix, unlocked.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Idempotent-Replayed": {
								"$ref": "#/components/headers/IdempotentReplayed"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/UnlockedFix"
								},
								"example": {
									"object": "fix",
									"issue_id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
									"prompt": "Your .env file is served publicly at https://app.example.com/.env.\n1. Remove .env from the public directory of the build…\n2. Rotate every secret it contained…",
									"ai_fix": null,
									"charged": true,
									"fixes_left": 82
								}
							}
						}
					},
					"400": {
						"description": "Error: `idempotency_key_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"idempotency_key_invalid": {
										"$ref": "#/components/examples/idempotency_key_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`, `fix_quota_exceeded`, `project_paused`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									},
									"fix_quota_exceeded": {
										"$ref": "#/components/examples/fix_quota_exceeded"
									},
									"project_paused": {
										"$ref": "#/components/examples/project_paused"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"409": {
						"description": "Error: `proof_required`, `idempotency_conflict`, `idempotency_in_progress`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"proof_required": {
										"$ref": "#/components/examples/proof_required"
									},
									"idempotency_conflict": {
										"$ref": "#/components/examples/idempotency_conflict"
									},
									"idempotency_in_progress": {
										"$ref": "#/components/examples/idempotency_in_progress"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			},
			"get": {
				"operationId": "getFix",
				"tags": [
					"Issues"
				],
				"summary": "Retrieve an unlocked fix",
				"description": "Reads the fix of an issue already unlocked (in the app, by the MCP server or by `POST /issues/{issue_id}/fix`). Free; never unlocks anything: a locked fix answers 404 `fix_locked`.",
				"x-oauth-scope": "scans:read",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "issue_id",
						"in": "path",
						"required": true,
						"description": "Issue id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The fix.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Fix"
								},
								"example": {
									"object": "fix",
									"issue_id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
									"prompt": "Your .env file is served publicly at https://app.example.com/.env.\n1. Remove .env from the public directory of the build…\n2. Rotate every secret it contained…",
									"ai_fix": null
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`, `fix_locked`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									},
									"fix_locked": {
										"$ref": "#/components/examples/fix_locked"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		},
		"/issues/{issue_id}/recheck": {
			"post": {
				"operationId": "recheckIssue",
				"tags": [
					"Issues"
				],
				"summary": "Recheck an issue",
				"description": "Checks again whether an issue is still there, typically after deploying its fix. Free, capped per project and per day. For a site, the same check runs now on the live site: `fixed` (the issue is closed) or `still_open`. For a repository, a new analysis of the default branch starts (push the fix first; at most one every 15 minutes) and the answer is `pending` with its `scan_id`: poll the scan, then read the issue.",
				"x-oauth-scope": "scans:run",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"parameters": [
					{
						"name": "issue_id",
						"in": "path",
						"required": true,
						"description": "Issue id.",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					},
					{
						"$ref": "#/components/parameters/IdempotencyKey"
					}
				],
				"responses": {
					"200": {
						"description": "The result of the check.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"X-RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"X-RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"X-RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Idempotent-Replayed": {
								"$ref": "#/components/headers/IdempotentReplayed"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Recheck"
								},
								"example": {
									"object": "recheck",
									"issue_id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
									"status": "fixed",
									"checked_at": "2026-10-13T09:15:40.000Z",
									"evidence": "GET /.env → 404",
									"scan_id": null
								}
							}
						}
					},
					"400": {
						"description": "Error: `idempotency_key_invalid`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"idempotency_key_invalid": {
										"$ref": "#/components/examples/idempotency_key_invalid"
									}
								}
							}
						}
					},
					"401": {
						"description": "Error: `authentication_required`, `invalid_api_key`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"authentication_required": {
										"$ref": "#/components/examples/authentication_required"
									},
									"invalid_api_key": {
										"$ref": "#/components/examples/invalid_api_key"
									}
								}
							}
						}
					},
					"402": {
						"description": "Error: `api_plan_required`, `api_trial_ended`, `project_paused`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"api_plan_required": {
										"$ref": "#/components/examples/api_plan_required"
									},
									"api_trial_ended": {
										"$ref": "#/components/examples/api_trial_ended"
									},
									"project_paused": {
										"$ref": "#/components/examples/project_paused"
									}
								}
							}
						}
					},
					"403": {
						"description": "Error: `scope_missing`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"scope_missing": {
										"$ref": "#/components/examples/scope_missing"
									}
								}
							}
						}
					},
					"404": {
						"description": "Error: `resource_not_found`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"resource_not_found": {
										"$ref": "#/components/examples/resource_not_found"
									}
								}
							}
						}
					},
					"409": {
						"description": "Error: `proof_required`, `idempotency_conflict`, `idempotency_in_progress`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"proof_required": {
										"$ref": "#/components/examples/proof_required"
									},
									"idempotency_conflict": {
										"$ref": "#/components/examples/idempotency_conflict"
									},
									"idempotency_in_progress": {
										"$ref": "#/components/examples/idempotency_in_progress"
									}
								}
							}
						}
					},
					"429": {
						"description": "Error: `rate_limited`, `recheck_limit_reached`, `repository_analyses_limit_reached`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							},
							"Retry-After": {
								"$ref": "#/components/headers/RetryAfter"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"rate_limited": {
										"$ref": "#/components/examples/rate_limited"
									},
									"recheck_limit_reached": {
										"$ref": "#/components/examples/recheck_limit_reached"
									},
									"repository_analyses_limit_reached": {
										"$ref": "#/components/examples/repository_analyses_limit_reached"
									}
								}
							}
						}
					},
					"500": {
						"description": "Error: `internal_error`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"internal_error": {
										"$ref": "#/components/examples/internal_error"
									}
								}
							}
						}
					},
					"502": {
						"description": "Error: `recheck_failed`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"recheck_failed": {
										"$ref": "#/components/examples/recheck_failed"
									}
								}
							}
						}
					},
					"503": {
						"description": "Error: `service_unavailable`.",
						"headers": {
							"Request-Id": {
								"$ref": "#/components/headers/RequestId"
							},
							"Rustine-Version": {
								"$ref": "#/components/headers/RustineVersion"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								},
								"examples": {
									"service_unavailable": {
										"$ref": "#/components/examples/service_unavailable"
									}
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"bearerFormat": "rst_… (API key) or rat_… (OAuth access token)",
				"description": "An API key has every scope. An OAuth access token needs the scope named by each operation (`x-oauth-scope`): `scans:read`, `scans:run`, `fixes:unlock`, `issues:write`."
			}
		},
		"examples": {
			"authentication_required": {
				"summary": "No credential: pass `Authorization: Bearer rst_…`.",
				"value": {
					"error": {
						"type": "authentication",
						"code": "authentication_required",
						"message": "No credential: pass `Authorization: Bearer rst_…`.",
						"doc_url": "https://docs.rustine.io/api/errors#authentication_required"
					}
				}
			},
			"invalid_api_key": {
				"summary": "The API key or OAuth access token is invalid, revoked or expired.",
				"value": {
					"error": {
						"type": "authentication",
						"code": "invalid_api_key",
						"message": "The API key or OAuth access token is invalid, revoked or expired.",
						"doc_url": "https://docs.rustine.io/api/errors#invalid_api_key"
					}
				}
			},
			"scope_missing": {
				"summary": "The OAuth access token lacks the scope this endpoint needs.",
				"value": {
					"error": {
						"type": "authentication",
						"code": "scope_missing",
						"message": "The OAuth access token lacks the scope this endpoint needs.",
						"doc_url": "https://docs.rustine.io/api/errors#scope_missing"
					}
				}
			},
			"api_plan_required": {
				"summary": "The API comes with the Pro plan (a 14-day trial with Solo); the workspace is on Free.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "api_plan_required",
						"message": "The API comes with the Pro plan (a 14-day trial with Solo); the workspace is on Free.",
						"doc_url": "https://docs.rustine.io/api/errors#api_plan_required",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"api_trial_ended": {
				"summary": "Solo's 14-day API and MCP trial is over: the API comes with the Pro plan.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "api_trial_ended",
						"message": "Solo's 14-day API and MCP trial is over: the API comes with the Pro plan.",
						"doc_url": "https://docs.rustine.io/api/errors#api_trial_ended",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"project_paused": {
				"summary": "The project is paused (the plan covers fewer projects than the workspace has): its issues stay readable, nothing new runs.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "project_paused",
						"message": "The project is paused (the plan covers fewer projects than the workspace has): its issues stay readable, nothing new runs.",
						"doc_url": "https://docs.rustine.io/api/errors#project_paused",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"project_limit_reached": {
				"summary": "The plan's projects are all in use.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "project_limit_reached",
						"message": "The plan's projects are all in use.",
						"doc_url": "https://docs.rustine.io/api/errors#project_limit_reached",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"address_limit_reached": {
				"summary": "The project already follows as many addresses as the plan allows.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "address_limit_reached",
						"message": "The project already follows as many addresses as the plan allows.",
						"doc_url": "https://docs.rustine.io/api/errors#address_limit_reached",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"domain_taken": {
				"summary": "On the Free plan, a domain another free account follows cannot be added or scanned.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "domain_taken",
						"message": "On the Free plan, a domain another free account follows cannot be added or scanned.",
						"doc_url": "https://docs.rustine.io/api/errors#domain_taken",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"audit_not_in_plan": {
				"summary": "The Free plan runs quick scans only; the full audit comes with Solo and Pro.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "audit_not_in_plan",
						"message": "The Free plan runs quick scans only; the full audit comes with Solo and Pro.",
						"doc_url": "https://docs.rustine.io/api/errors#audit_not_in_plan",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"repositories_not_in_plan": {
				"summary": "Repository analyses come with Solo and Pro.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "repositories_not_in_plan",
						"message": "Repository analyses come with Solo and Pro.",
						"doc_url": "https://docs.rustine.io/api/errors#repositories_not_in_plan",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"scan_quota_exceeded": {
				"summary": "This period's quick scans are used up (and no pack credit is left).",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "scan_quota_exceeded",
						"message": "This period's quick scans are used up (and no pack credit is left).",
						"doc_url": "https://docs.rustine.io/api/errors#scan_quota_exceeded",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"audit_quota_exceeded": {
				"summary": "This period's full audits are used up (and no pack credit is left).",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "audit_quota_exceeded",
						"message": "This period's full audits are used up (and no pack credit is left).",
						"doc_url": "https://docs.rustine.io/api/errors#audit_quota_exceeded",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"fix_quota_exceeded": {
				"summary": "This period's fixes are used up (and no pack credit is left).",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "fix_quota_exceeded",
						"message": "This period's fixes are used up (and no pack credit is left).",
						"doc_url": "https://docs.rustine.io/api/errors#fix_quota_exceeded",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"plan_limit": {
				"summary": "The plan does not include this.",
				"value": {
					"error": {
						"type": "plan_limit",
						"code": "plan_limit",
						"message": "The plan does not include this.",
						"doc_url": "https://docs.rustine.io/api/errors#plan_limit",
						"upgrade_url": "https://app.rustine.io/settings?section=offer"
					}
				}
			},
			"parameter_invalid": {
				"summary": "A parameter is missing or invalid; `param` names it.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "parameter_invalid",
						"message": "A parameter is missing or invalid; `param` names it.",
						"doc_url": "https://docs.rustine.io/api/errors#parameter_invalid"
					}
				}
			},
			"body_invalid": {
				"summary": "The request body is not valid JSON.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "body_invalid",
						"message": "The request body is not valid JSON.",
						"doc_url": "https://docs.rustine.io/api/errors#body_invalid"
					}
				}
			},
			"cursor_invalid": {
				"summary": "`starting_after` is not the id of an object of this list.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "cursor_invalid",
						"message": "`starting_after` is not the id of an object of this list.",
						"doc_url": "https://docs.rustine.io/api/errors#cursor_invalid"
					}
				}
			},
			"url_invalid": {
				"summary": "The address is not a valid public site URL.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "url_invalid",
						"message": "The address is not a valid public site URL.",
						"doc_url": "https://docs.rustine.io/api/errors#url_invalid"
					}
				}
			},
			"url_private": {
				"summary": "Rustine only scans public sites, never a local or private address.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "url_private",
						"message": "Rustine only scans public sites, never a local or private address.",
						"doc_url": "https://docs.rustine.io/api/errors#url_private"
					}
				}
			},
			"target_kind_invalid": {
				"summary": "The scan kind does not fit the target: `repository` for a GitHub repository, `quick` or `audit` for a site.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "target_kind_invalid",
						"message": "The scan kind does not fit the target: `repository` for a GitHub repository, `quick` or `audit` for a site.",
						"doc_url": "https://docs.rustine.io/api/errors#target_kind_invalid"
					}
				}
			},
			"reason_required": {
				"summary": "Marking a false positive or an accepted risk needs a reason (10 to 500 characters).",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "reason_required",
						"message": "Marking a false positive or an accepted risk needs a reason (10 to 500 characters).",
						"doc_url": "https://docs.rustine.io/api/errors#reason_required"
					}
				}
			},
			"idempotency_key_invalid": {
				"summary": "The Idempotency-Key header must be 1 to 255 printable characters.",
				"value": {
					"error": {
						"type": "invalid_request",
						"code": "idempotency_key_invalid",
						"message": "The Idempotency-Key header must be 1 to 255 printable characters.",
						"doc_url": "https://docs.rustine.io/api/errors#idempotency_key_invalid"
					}
				}
			},
			"resource_not_found": {
				"summary": "No such object in the workspace of the key (ids of other workspaces are never revealed).",
				"value": {
					"error": {
						"type": "not_found",
						"code": "resource_not_found",
						"message": "No such object in the workspace of the key (ids of other workspaces are never revealed).",
						"doc_url": "https://docs.rustine.io/api/errors#resource_not_found"
					}
				}
			},
			"fix_locked": {
				"summary": "The fix of this issue is not unlocked yet: `POST /issues/{issue_id}/fix` unlocks it.",
				"value": {
					"error": {
						"type": "not_found",
						"code": "fix_locked",
						"message": "The fix of this issue is not unlocked yet: `POST /issues/{issue_id}/fix` unlocks it.",
						"doc_url": "https://docs.rustine.io/api/errors#fix_locked"
					}
				}
			},
			"route_not_found": {
				"summary": "No such endpoint.",
				"value": {
					"error": {
						"type": "not_found",
						"code": "route_not_found",
						"message": "No such endpoint.",
						"doc_url": "https://docs.rustine.io/api/errors#route_not_found"
					}
				}
			},
			"proof_required": {
				"summary": "The site must be verified as the workspace's first (full audits, fixes, marking issues).",
				"value": {
					"error": {
						"type": "conflict",
						"code": "proof_required",
						"message": "The site must be verified as the workspace's first (full audits, fixes, marking issues).",
						"doc_url": "https://docs.rustine.io/api/errors#proof_required"
					}
				}
			},
			"site_excluded": {
				"summary": "The owner of this site asked not to be scanned.",
				"value": {
					"error": {
						"type": "conflict",
						"code": "site_excluded",
						"message": "The owner of this site asked not to be scanned.",
						"doc_url": "https://docs.rustine.io/api/errors#site_excluded"
					}
				}
			},
			"target_exists": {
				"summary": "The project already follows this address.",
				"value": {
					"error": {
						"type": "conflict",
						"code": "target_exists",
						"message": "The project already follows this address.",
						"doc_url": "https://docs.rustine.io/api/errors#target_exists"
					}
				}
			},
			"issue_already_fixed": {
				"summary": "The issue is fixed: there is nothing to mark.",
				"value": {
					"error": {
						"type": "conflict",
						"code": "issue_already_fixed",
						"message": "The issue is fixed: there is nothing to mark.",
						"doc_url": "https://docs.rustine.io/api/errors#issue_already_fixed"
					}
				}
			},
			"github_unavailable": {
				"summary": "Repository analyses are not available on this platform right now.",
				"value": {
					"error": {
						"type": "conflict",
						"code": "github_unavailable",
						"message": "Repository analyses are not available on this platform right now.",
						"doc_url": "https://docs.rustine.io/api/errors#github_unavailable"
					}
				}
			},
			"idempotency_conflict": {
				"summary": "This Idempotency-Key was already used with another request (method, path or body).",
				"value": {
					"error": {
						"type": "conflict",
						"code": "idempotency_conflict",
						"message": "This Idempotency-Key was already used with another request (method, path or body).",
						"doc_url": "https://docs.rustine.io/api/errors#idempotency_conflict"
					}
				}
			},
			"idempotency_in_progress": {
				"summary": "A request with this Idempotency-Key is still being processed: retry in a moment.",
				"value": {
					"error": {
						"type": "conflict",
						"code": "idempotency_in_progress",
						"message": "A request with this Idempotency-Key is still being processed: retry in a moment.",
						"doc_url": "https://docs.rustine.io/api/errors#idempotency_in_progress"
					}
				}
			},
			"conflict": {
				"summary": "The request conflicts with the current state.",
				"value": {
					"error": {
						"type": "conflict",
						"code": "conflict",
						"message": "The request conflicts with the current state.",
						"doc_url": "https://docs.rustine.io/api/errors#conflict"
					}
				}
			},
			"rate_limited": {
				"summary": "Too many requests for this key (120 per minute): wait `Retry-After` seconds.",
				"value": {
					"error": {
						"type": "rate_limited",
						"code": "rate_limited",
						"message": "Too many requests for this key (120 per minute): wait `Retry-After` seconds.",
						"doc_url": "https://docs.rustine.io/api/errors#rate_limited"
					}
				}
			},
			"recheck_limit_reached": {
				"summary": "Too many rechecks on this project today.",
				"value": {
					"error": {
						"type": "rate_limited",
						"code": "recheck_limit_reached",
						"message": "Too many rechecks on this project today.",
						"doc_url": "https://docs.rustine.io/api/errors#recheck_limit_reached"
					}
				}
			},
			"repository_analyses_limit_reached": {
				"summary": "The workspace started many repository analyses in the last 24 hours.",
				"value": {
					"error": {
						"type": "rate_limited",
						"code": "repository_analyses_limit_reached",
						"message": "The workspace started many repository analyses in the last 24 hours.",
						"doc_url": "https://docs.rustine.io/api/errors#repository_analyses_limit_reached"
					}
				}
			},
			"platform_busy": {
				"summary": "Rustine is not starting new scans for a few minutes: retry later.",
				"value": {
					"error": {
						"type": "rate_limited",
						"code": "platform_busy",
						"message": "Rustine is not starting new scans for a few minutes: retry later.",
						"doc_url": "https://docs.rustine.io/api/errors#platform_busy"
					}
				}
			},
			"recheck_failed": {
				"summary": "The recheck could not complete (site unreachable?).",
				"value": {
					"error": {
						"type": "api_error",
						"code": "recheck_failed",
						"message": "The recheck could not complete (site unreachable?).",
						"doc_url": "https://docs.rustine.io/api/errors#recheck_failed"
					}
				}
			},
			"service_unavailable": {
				"summary": "Rustine is unavailable for a moment: retry with the same Idempotency-Key.",
				"value": {
					"error": {
						"type": "api_error",
						"code": "service_unavailable",
						"message": "Rustine is unavailable for a moment: retry with the same Idempotency-Key.",
						"doc_url": "https://docs.rustine.io/api/errors#service_unavailable"
					}
				}
			},
			"internal_error": {
				"summary": "Something went wrong on Rustine's side; it is reported automatically.",
				"value": {
					"error": {
						"type": "api_error",
						"code": "internal_error",
						"message": "Something went wrong on Rustine's side; it is reported automatically.",
						"doc_url": "https://docs.rustine.io/api/errors#internal_error"
					}
				}
			}
		},
		"parameters": {
			"IdempotencyKey": {
				"name": "Idempotency-Key",
				"in": "header",
				"required": false,
				"description": "A unique key (1 to 255 printable characters, a UUID is fine) to retry this request safely: the first response is replayed for 24 hours; the same key with another request answers 409 `idempotency_conflict`.",
				"schema": {
					"type": "string",
					"minLength": 1,
					"maxLength": 255
				}
			}
		},
		"headers": {
			"RequestId": {
				"description": "Identifier of the request; give it to support.",
				"schema": {
					"type": "string",
					"examples": [
						"req_6f1c0d9a2b7e4c3d5a8b9e0f"
					]
				}
			},
			"RustineVersion": {
				"description": "Version of the API contract that answered.",
				"schema": {
					"type": "string",
					"examples": [
						"2026-10-13"
					]
				}
			},
			"RateLimitLimit": {
				"description": "Requests allowed per minute for this key.",
				"schema": {
					"type": "integer"
				}
			},
			"RateLimitRemaining": {
				"description": "Requests left in the current window.",
				"schema": {
					"type": "integer"
				}
			},
			"RateLimitReset": {
				"description": "When the window resets (Unix time, seconds).",
				"schema": {
					"type": "integer"
				}
			},
			"RetryAfter": {
				"description": "Seconds to wait before retrying.",
				"schema": {
					"type": "integer"
				}
			},
			"IdempotentReplayed": {
				"description": "`true` when the response is the replay of an earlier request with the same Idempotency-Key.",
				"schema": {
					"type": "string",
					"enum": [
						"true"
					]
				}
			}
		},
		"schemas": {
			"Account": {
				"type": "object",
				"properties": {
					"object": {
						"type": "string",
						"enum": [
							"account"
						]
					},
					"workspace": {
						"type": "object",
						"properties": {
							"id": {
								"type": "string",
								"format": "uuid",
								"description": "Workspace id."
							},
							"name": {
								"type": "string",
								"description": "Workspace name."
							}
						},
						"required": [
							"id",
							"name"
						],
						"additionalProperties": false
					},
					"plan": {
						"enum": [
							"free",
							"solo",
							"pro"
						],
						"description": "The plan in force (Free, Solo or Pro).",
						"type": "string"
					},
					"api_access": {
						"type": "object",
						"properties": {
							"allowed": {
								"description": "Whether the API and the MCP server answer now.",
								"type": "boolean"
							},
							"trial_ends_at": {
								"type": [
									"string",
									"null"
								],
								"format": "date-time",
								"description": "Solo only: end of the 14-day trial once started; null on Pro, or before the first call."
							}
						},
						"required": [
							"allowed",
							"trial_ends_at"
						],
						"additionalProperties": false
					},
					"period": {
						"type": "object",
						"properties": {
							"start": {
								"type": "string",
								"format": "date-time",
								"description": "Start of the current 30-day quota period."
							},
							"end": {
								"type": "string",
								"format": "date-time",
								"description": "When the counters reset."
							}
						},
						"required": [
							"start",
							"end"
						],
						"additionalProperties": false
					},
					"quotas": {
						"type": "object",
						"properties": {
							"quick_scans": {
								"type": "object",
								"properties": {
									"limit": {
										"type": "integer",
										"description": "Units included this period (all the projects the workspace pays for)."
									},
									"used": {
										"type": "integer",
										"description": "Units used this period."
									},
									"remaining": {
										"type": "integer",
										"description": "Units left this period, before pack credits."
									}
								},
								"required": [
									"limit",
									"used",
									"remaining"
								],
								"additionalProperties": false
							},
							"audits": {
								"type": "object",
								"properties": {
									"limit": {
										"type": "integer",
										"description": "Units included this period (all the projects the workspace pays for)."
									},
									"used": {
										"type": "integer",
										"description": "Units used this period."
									},
									"remaining": {
										"type": "integer",
										"description": "Units left this period, before pack credits."
									}
								},
								"required": [
									"limit",
									"used",
									"remaining"
								],
								"additionalProperties": false
							},
							"fixes": {
								"type": "object",
								"properties": {
									"limit": {
										"type": "integer",
										"description": "Units included this period (all the projects the workspace pays for)."
									},
									"used": {
										"type": "integer",
										"description": "Units used this period."
									},
									"remaining": {
										"type": "integer",
										"description": "Units left this period, before pack credits."
									}
								},
								"required": [
									"limit",
									"used",
									"remaining"
								],
								"additionalProperties": false
							}
						},
						"required": [
							"quick_scans",
							"audits",
							"fixes"
						],
						"additionalProperties": false
					},
					"free_fixes_left": {
						"type": "integer",
						"description": "Fixes given once to a Free workspace, still unused."
					},
					"pack_credits": {
						"type": "object",
						"properties": {
							"scans": {
								"type": "integer",
								"description": "Scan credits from packs, used once the period's quota is spent."
							},
							"fixes": {
								"type": "integer",
								"description": "Fix credits from packs, used once the period's quota is spent."
							}
						},
						"required": [
							"scans",
							"fixes"
						],
						"additionalProperties": false
					},
					"projects": {
						"type": "object",
						"properties": {
							"limit": {
								"type": "integer",
								"description": "Open projects allowed by the plan (included and extra)."
							},
							"used": {
								"type": "integer",
								"description": "Projects in the workspace, paused ones included."
							},
							"paused": {
								"type": "integer",
								"description": "Projects paused because the plan covers fewer projects."
							}
						},
						"required": [
							"limit",
							"used",
							"paused"
						],
						"additionalProperties": false
					},
					"addresses_per_project": {
						"type": "integer",
						"description": "Addresses (hostnames) a project may follow on this plan."
					},
					"features": {
						"type": "object",
						"properties": {
							"full_audit": {
								"description": "Full audits (Solo, Pro).",
								"type": "boolean"
							},
							"repositories": {
								"description": "GitHub repository analyses (Solo, Pro).",
								"type": "boolean"
							},
							"ai_fix": {
								"description": "AI Fix, Rustine writes the patch (Solo, Pro).",
								"type": "boolean"
							},
							"alerts": {
								"description": "Score-drop and new-advisory alerts (Pro).",
								"type": "boolean"
							}
						},
						"required": [
							"full_audit",
							"repositories",
							"ai_fix",
							"alerts"
						],
						"additionalProperties": false
					}
				},
				"required": [
					"object",
					"workspace",
					"plan",
					"api_access",
					"period",
					"quotas",
					"free_fixes_left",
					"pack_credits",
					"projects",
					"addresses_per_project",
					"features"
				],
				"additionalProperties": false
			},
			"Project": {
				"type": "object",
				"properties": {
					"id": {
						"type": "string",
						"format": "uuid",
						"description": "Project id."
					},
					"object": {
						"type": "string",
						"enum": [
							"project"
						]
					},
					"name": {
						"type": "string",
						"description": "Project name."
					},
					"score": {
						"type": [
							"number",
							"null"
						],
						"description": "Score out of 100; null before the first finished scan."
					},
					"grade": {
						"anyOf": [
							{
								"enum": [
									"A",
									"B",
									"C",
									"D",
									"F"
								],
								"type": "string"
							},
							{
								"type": "null"
							}
						],
						"description": "Grade from A to F; null before the first finished scan."
					},
					"open_issues": {
						"type": "integer",
						"description": "Open issues across the project's targets, hidden ones included."
					},
					"paused": {
						"description": "Paused because the plan covers fewer projects: readable, but no scan, recheck, fix or new address.",
						"type": "boolean"
					},
					"last_scan_at": {
						"type": [
							"string",
							"null"
						],
						"format": "date-time",
						"description": "End of the last finished scan of any target."
					},
					"targets": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/Target"
						}
					}
				},
				"required": [
					"id",
					"object",
					"name",
					"score",
					"grade",
					"open_issues",
					"paused",
					"last_scan_at",
					"targets"
				],
				"additionalProperties": false
			},
			"Target": {
				"type": "object",
				"properties": {
					"id": {
						"type": "string",
						"format": "uuid",
						"description": "Target id: a site (domain) or a GitHub repository of the project."
					},
					"object": {
						"type": "string",
						"enum": [
							"target"
						]
					},
					"project_id": {
						"type": "string",
						"format": "uuid",
						"description": "Project the target belongs to."
					},
					"kind": {
						"enum": [
							"domain",
							"server",
							"repository"
						],
						"description": "`domain` (a site) or `repository`.",
						"type": "string"
					},
					"address": {
						"type": "string",
						"description": "Hostname of a site (`app.example.com`) or `owner/repo` of a repository."
					},
					"verified": {
						"description": "Whether the workspace proved it controls this target. Without proof, issue details stay hidden and only quick scans run.",
						"type": "boolean"
					},
					"score": {
						"type": [
							"number",
							"null"
						],
						"description": "Score out of 100; null before the first finished scan."
					},
					"grade": {
						"anyOf": [
							{
								"enum": [
									"A",
									"B",
									"C",
									"D",
									"F"
								],
								"type": "string"
							},
							{
								"type": "null"
							}
						],
						"description": "Grade from A to F; null before the first finished scan."
					},
					"last_scan_at": {
						"type": [
							"string",
							"null"
						],
						"format": "date-time",
						"description": "End of the last finished scan."
					},
					"hidden_issues": {
						"type": [
							"object",
							"null"
						],
						"properties": {
							"urgent": {
								"type": "integer",
								"description": "Open critical and high issues hidden until the site is verified."
							},
							"later": {
								"type": "integer",
								"description": "Open medium and low issues hidden until the site is verified."
							}
						},
						"required": [
							"urgent",
							"later"
						],
						"additionalProperties": false,
						"description": "Unverified site: what its hidden issues amount to, counts only (never which, nor where). Null when nothing is hidden."
					}
				},
				"required": [
					"id",
					"object",
					"project_id",
					"kind",
					"address",
					"verified",
					"score",
					"grade",
					"last_scan_at",
					"hidden_issues"
				],
				"additionalProperties": false
			},
			"Scan": {
				"type": "object",
				"properties": {
					"id": {
						"type": "string",
						"format": "uuid",
						"description": "Scan id."
					},
					"object": {
						"type": "string",
						"enum": [
							"scan"
						]
					},
					"target_id": {
						"type": "string",
						"format": "uuid",
						"description": "Target scanned."
					},
					"kind": {
						"enum": [
							"quick",
							"audit",
							"repository"
						],
						"description": "`quick` (what any visitor could see), `audit` (adds deeper read-only checks, verified sites only) or `repository` (analysis of a GitHub repository).",
						"type": "string"
					},
					"status": {
						"enum": [
							"queued",
							"running",
							"done",
							"failed"
						],
						"description": "Scans run asynchronously: poll until `done` or `failed`.",
						"type": "string"
					},
					"score": {
						"type": [
							"number",
							"null"
						],
						"description": "Score out of 100, once done."
					},
					"grade": {
						"anyOf": [
							{
								"enum": [
									"A",
									"B",
									"C",
									"D",
									"F"
								],
								"type": "string"
							},
							{
								"type": "null"
							}
						],
						"description": "Grade, once done."
					},
					"coverage": {
						"type": [
							"number",
							"null"
						],
						"description": "Share of the checks that could run (0 to 1), once done."
					},
					"audit_fallback": {
						"description": "An audit was asked but the site's proof no longer held: a quick scan ran instead and no audit was counted.",
						"type": "boolean"
					},
					"reused": {
						"description": "True when the request got an existing scan instead of a new one (one already running, or one of the same kind in the last hour); nothing was counted.",
						"type": "boolean"
					},
					"next_real_scan_at": {
						"type": [
							"string",
							"null"
						],
						"format": "date-time",
						"description": "When `reused` because of a recent scan: when a new scan of this kind may start."
					},
					"created_at": {
						"type": "string",
						"format": "date-time",
						"description": "When the scan was requested."
					},
					"finished_at": {
						"type": [
							"string",
							"null"
						],
						"format": "date-time",
						"description": "When the scan finished."
					}
				},
				"required": [
					"id",
					"object",
					"target_id",
					"kind",
					"status",
					"score",
					"grade",
					"coverage",
					"audit_fallback",
					"reused",
					"next_real_scan_at",
					"created_at",
					"finished_at"
				],
				"additionalProperties": false
			},
			"Issue": {
				"type": "object",
				"properties": {
					"id": {
						"type": "string",
						"format": "uuid",
						"description": "Issue id; it keeps its identity from one scan to the next."
					},
					"object": {
						"type": "string",
						"enum": [
							"issue"
						]
					},
					"project_id": {
						"type": "string",
						"format": "uuid",
						"description": "Project of the issue."
					},
					"target_id": {
						"type": "string",
						"format": "uuid",
						"description": "Target where it was found."
					},
					"address": {
						"type": "string",
						"description": "Hostname of the site or `owner/repo`."
					},
					"control_id": {
						"type": "string",
						"description": "The check that found it (stable identifier)."
					},
					"family": {
						"enum": [
							"database",
							"secrets",
							"exposedFiles",
							"personalData",
							"vulnerableVersions",
							"configuration",
							"compliance",
							"server",
							"code"
						],
						"description": "Family of the check.",
						"type": "string"
					},
					"severity": {
						"enum": [
							"critical",
							"high",
							"medium",
							"low",
							"info"
						],
						"type": "string"
					},
					"confidence": {
						"enum": [
							"confirmed",
							"likely",
							"estimated"
						],
						"description": "How sure the finding is.",
						"type": "string"
					},
					"status": {
						"enum": [
							"open",
							"fixed",
							"false_positive",
							"accepted_risk"
						],
						"description": "`open`; `fixed` (a later scan or recheck no longer finds it); `false_positive` (left out of the score); `accepted_risk` (kept in the score).",
						"type": "string"
					},
					"title": {
						"type": "string",
						"description": "One line, in the language of the key's owner."
					},
					"explanation": {
						"type": "string",
						"description": "The risk in plain words, in the key owner's language."
					},
					"detail": {
						"type": "string",
						"description": "Technical detail, in the key owner's language."
					},
					"location": {
						"type": "string",
						"description": "Where: a path, a table, or `file:line@commit` in a repository."
					},
					"file": {
						"type": [
							"string",
							"null"
						],
						"description": "Repository issue: the file."
					},
					"line": {
						"type": [
							"integer",
							"null"
						],
						"description": "Repository issue: the line."
					},
					"commit": {
						"type": [
							"string",
							"null"
						],
						"description": "Repository issue: the commit analysed."
					},
					"evidence": {
						"type": "string",
						"description": "What the check observed, secrets masked."
					},
					"fix_unlocked": {
						"description": "Whether its fix is unlocked (readable for free).",
						"type": "boolean"
					},
					"status_reason": {
						"type": [
							"string",
							"null"
						],
						"description": "Why it was marked a false positive or an accepted risk."
					},
					"marked_by": {
						"type": [
							"string",
							"null"
						],
						"description": "Who marked it: an agent, an API key's name, or `app`."
					},
					"first_seen_at": {
						"type": "string",
						"format": "date-time",
						"description": "First scan that found it."
					},
					"last_seen_at": {
						"type": "string",
						"format": "date-time",
						"description": "Last scan that found it."
					},
					"fixed_at": {
						"type": [
							"string",
							"null"
						],
						"format": "date-time",
						"description": "When it was found fixed."
					}
				},
				"required": [
					"id",
					"object",
					"project_id",
					"target_id",
					"address",
					"control_id",
					"family",
					"severity",
					"confidence",
					"status",
					"title",
					"explanation",
					"detail",
					"location",
					"file",
					"line",
					"commit",
					"evidence",
					"fix_unlocked",
					"status_reason",
					"marked_by",
					"first_seen_at",
					"last_seen_at",
					"fixed_at"
				],
				"additionalProperties": false
			},
			"Fix": {
				"type": "object",
				"properties": {
					"object": {
						"type": "string",
						"enum": [
							"fix"
						]
					},
					"issue_id": {
						"type": "string",
						"format": "uuid",
						"description": "The issue."
					},
					"prompt": {
						"type": "string",
						"description": "The fix as instructions for a coding agent (Cursor, Claude Code…), to apply in the codebase. Rustine never edits code."
					},
					"ai_fix": {
						"type": [
							"string",
							"null"
						],
						"description": "A patch written by Rustine (unified diff), when one was generated in the app."
					}
				},
				"required": [
					"object",
					"issue_id",
					"prompt",
					"ai_fix"
				],
				"additionalProperties": false
			},
			"UnlockedFix": {
				"type": "object",
				"properties": {
					"object": {
						"type": "string",
						"enum": [
							"fix"
						]
					},
					"issue_id": {
						"type": "string",
						"format": "uuid",
						"description": "The issue."
					},
					"prompt": {
						"type": "string",
						"description": "The fix as instructions for a coding agent."
					},
					"ai_fix": {
						"type": [
							"string",
							"null"
						],
						"description": "A patch written by Rustine, if any."
					},
					"charged": {
						"description": "True when this call unlocked the fix and consumed one; false when it was already unlocked.",
						"type": "boolean"
					},
					"fixes_left": {
						"type": "integer",
						"description": "Fixes left this period after this call (quota plus pack credits)."
					}
				},
				"required": [
					"object",
					"issue_id",
					"prompt",
					"ai_fix",
					"charged",
					"fixes_left"
				],
				"additionalProperties": false
			},
			"Recheck": {
				"type": "object",
				"properties": {
					"object": {
						"type": "string",
						"enum": [
							"recheck"
						]
					},
					"issue_id": {
						"type": "string",
						"format": "uuid",
						"description": "The issue."
					},
					"status": {
						"enum": [
							"fixed",
							"still_open",
							"pending"
						],
						"description": "`fixed` (closed), `still_open`, or `pending` for a repository issue: a new analysis of the default branch was started (`scan_id`); poll it, then read the issue.",
						"type": "string"
					},
					"checked_at": {
						"type": "string",
						"format": "date-time",
						"description": "When the check ran."
					},
					"evidence": {
						"type": "string",
						"description": "What the check saw this time."
					},
					"scan_id": {
						"type": [
							"string",
							"null"
						],
						"format": "uuid",
						"description": "Repository issue: the analysis that settles it."
					}
				},
				"required": [
					"object",
					"issue_id",
					"status",
					"checked_at",
					"evidence",
					"scan_id"
				],
				"additionalProperties": false
			},
			"Error": {
				"type": "object",
				"properties": {
					"error": {
						"type": "object",
						"properties": {
							"type": {
								"enum": [
									"invalid_request",
									"authentication",
									"plan_limit",
									"not_found",
									"rate_limited",
									"conflict",
									"api_error"
								],
								"type": "string"
							},
							"code": {
								"type": "string",
								"description": "Stable snake_case code; see doc_url."
							},
							"message": {
								"type": "string",
								"description": "A sentence for humans, in the key owner's language."
							},
							"param": {
								"type": "string",
								"description": "The parameter at fault (invalid_request)."
							},
							"doc_url": {
								"type": "string",
								"format": "uri",
								"description": "Where this code is documented."
							},
							"upgrade_url": {
								"type": "string",
								"format": "uri",
								"description": "plan_limit only: where to change plans or buy a pack."
							}
						},
						"required": [
							"type",
							"code",
							"message",
							"doc_url"
						],
						"additionalProperties": false
					}
				},
				"required": [
					"error"
				],
				"additionalProperties": false
			}
		}
	},
	"x-error-types": [
		"invalid_request",
		"authentication",
		"plan_limit",
		"not_found",
		"rate_limited",
		"conflict",
		"api_error"
	],
	"x-error-codes": {
		"authentication_required": {
			"type": "authentication",
			"status": 401,
			"description": "No credential: pass `Authorization: Bearer rst_…`.",
			"doc_url": "https://docs.rustine.io/api/errors#authentication_required"
		},
		"invalid_api_key": {
			"type": "authentication",
			"status": 401,
			"description": "The API key or OAuth access token is invalid, revoked or expired.",
			"doc_url": "https://docs.rustine.io/api/errors#invalid_api_key"
		},
		"scope_missing": {
			"type": "authentication",
			"status": 403,
			"description": "The OAuth access token lacks the scope this endpoint needs.",
			"doc_url": "https://docs.rustine.io/api/errors#scope_missing"
		},
		"api_plan_required": {
			"type": "plan_limit",
			"status": 402,
			"description": "The API comes with the Pro plan (a 14-day trial with Solo); the workspace is on Free.",
			"doc_url": "https://docs.rustine.io/api/errors#api_plan_required"
		},
		"api_trial_ended": {
			"type": "plan_limit",
			"status": 402,
			"description": "Solo's 14-day API and MCP trial is over: the API comes with the Pro plan.",
			"doc_url": "https://docs.rustine.io/api/errors#api_trial_ended"
		},
		"project_paused": {
			"type": "plan_limit",
			"status": 402,
			"description": "The project is paused (the plan covers fewer projects than the workspace has): its issues stay readable, nothing new runs.",
			"doc_url": "https://docs.rustine.io/api/errors#project_paused"
		},
		"project_limit_reached": {
			"type": "plan_limit",
			"status": 402,
			"description": "The plan's projects are all in use.",
			"doc_url": "https://docs.rustine.io/api/errors#project_limit_reached"
		},
		"address_limit_reached": {
			"type": "plan_limit",
			"status": 402,
			"description": "The project already follows as many addresses as the plan allows.",
			"doc_url": "https://docs.rustine.io/api/errors#address_limit_reached"
		},
		"domain_taken": {
			"type": "plan_limit",
			"status": 402,
			"description": "On the Free plan, a domain another free account follows cannot be added or scanned.",
			"doc_url": "https://docs.rustine.io/api/errors#domain_taken"
		},
		"audit_not_in_plan": {
			"type": "plan_limit",
			"status": 402,
			"description": "The Free plan runs quick scans only; the full audit comes with Solo and Pro.",
			"doc_url": "https://docs.rustine.io/api/errors#audit_not_in_plan"
		},
		"repositories_not_in_plan": {
			"type": "plan_limit",
			"status": 402,
			"description": "Repository analyses come with Solo and Pro.",
			"doc_url": "https://docs.rustine.io/api/errors#repositories_not_in_plan"
		},
		"scan_quota_exceeded": {
			"type": "plan_limit",
			"status": 402,
			"description": "This period's quick scans are used up (and no pack credit is left).",
			"doc_url": "https://docs.rustine.io/api/errors#scan_quota_exceeded"
		},
		"audit_quota_exceeded": {
			"type": "plan_limit",
			"status": 402,
			"description": "This period's full audits are used up (and no pack credit is left).",
			"doc_url": "https://docs.rustine.io/api/errors#audit_quota_exceeded"
		},
		"fix_quota_exceeded": {
			"type": "plan_limit",
			"status": 402,
			"description": "This period's fixes are used up (and no pack credit is left).",
			"doc_url": "https://docs.rustine.io/api/errors#fix_quota_exceeded"
		},
		"plan_limit": {
			"type": "plan_limit",
			"status": 402,
			"description": "The plan does not include this.",
			"doc_url": "https://docs.rustine.io/api/errors#plan_limit"
		},
		"parameter_invalid": {
			"type": "invalid_request",
			"status": 400,
			"description": "A parameter is missing or invalid; `param` names it.",
			"doc_url": "https://docs.rustine.io/api/errors#parameter_invalid"
		},
		"body_invalid": {
			"type": "invalid_request",
			"status": 400,
			"description": "The request body is not valid JSON.",
			"doc_url": "https://docs.rustine.io/api/errors#body_invalid"
		},
		"cursor_invalid": {
			"type": "invalid_request",
			"status": 400,
			"description": "`starting_after` is not the id of an object of this list.",
			"doc_url": "https://docs.rustine.io/api/errors#cursor_invalid"
		},
		"url_invalid": {
			"type": "invalid_request",
			"status": 400,
			"description": "The address is not a valid public site URL.",
			"doc_url": "https://docs.rustine.io/api/errors#url_invalid"
		},
		"url_private": {
			"type": "invalid_request",
			"status": 400,
			"description": "Rustine only scans public sites, never a local or private address.",
			"doc_url": "https://docs.rustine.io/api/errors#url_private"
		},
		"target_kind_invalid": {
			"type": "invalid_request",
			"status": 400,
			"description": "The scan kind does not fit the target: `repository` for a GitHub repository, `quick` or `audit` for a site.",
			"doc_url": "https://docs.rustine.io/api/errors#target_kind_invalid"
		},
		"reason_required": {
			"type": "invalid_request",
			"status": 400,
			"description": "Marking a false positive or an accepted risk needs a reason (10 to 500 characters).",
			"doc_url": "https://docs.rustine.io/api/errors#reason_required"
		},
		"idempotency_key_invalid": {
			"type": "invalid_request",
			"status": 400,
			"description": "The Idempotency-Key header must be 1 to 255 printable characters.",
			"doc_url": "https://docs.rustine.io/api/errors#idempotency_key_invalid"
		},
		"resource_not_found": {
			"type": "not_found",
			"status": 404,
			"description": "No such object in the workspace of the key (ids of other workspaces are never revealed).",
			"doc_url": "https://docs.rustine.io/api/errors#resource_not_found"
		},
		"fix_locked": {
			"type": "not_found",
			"status": 404,
			"description": "The fix of this issue is not unlocked yet: `POST /issues/{issue_id}/fix` unlocks it.",
			"doc_url": "https://docs.rustine.io/api/errors#fix_locked"
		},
		"route_not_found": {
			"type": "not_found",
			"status": 404,
			"description": "No such endpoint.",
			"doc_url": "https://docs.rustine.io/api/errors#route_not_found"
		},
		"proof_required": {
			"type": "conflict",
			"status": 409,
			"description": "The site must be verified as the workspace's first (full audits, fixes, marking issues).",
			"doc_url": "https://docs.rustine.io/api/errors#proof_required"
		},
		"site_excluded": {
			"type": "conflict",
			"status": 409,
			"description": "The owner of this site asked not to be scanned.",
			"doc_url": "https://docs.rustine.io/api/errors#site_excluded"
		},
		"target_exists": {
			"type": "conflict",
			"status": 409,
			"description": "The project already follows this address.",
			"doc_url": "https://docs.rustine.io/api/errors#target_exists"
		},
		"issue_already_fixed": {
			"type": "conflict",
			"status": 409,
			"description": "The issue is fixed: there is nothing to mark.",
			"doc_url": "https://docs.rustine.io/api/errors#issue_already_fixed"
		},
		"github_unavailable": {
			"type": "conflict",
			"status": 409,
			"description": "Repository analyses are not available on this platform right now.",
			"doc_url": "https://docs.rustine.io/api/errors#github_unavailable"
		},
		"idempotency_conflict": {
			"type": "conflict",
			"status": 409,
			"description": "This Idempotency-Key was already used with another request (method, path or body).",
			"doc_url": "https://docs.rustine.io/api/errors#idempotency_conflict"
		},
		"idempotency_in_progress": {
			"type": "conflict",
			"status": 409,
			"description": "A request with this Idempotency-Key is still being processed: retry in a moment.",
			"doc_url": "https://docs.rustine.io/api/errors#idempotency_in_progress"
		},
		"conflict": {
			"type": "conflict",
			"status": 409,
			"description": "The request conflicts with the current state.",
			"doc_url": "https://docs.rustine.io/api/errors#conflict"
		},
		"rate_limited": {
			"type": "rate_limited",
			"status": 429,
			"description": "Too many requests for this key (120 per minute): wait `Retry-After` seconds.",
			"doc_url": "https://docs.rustine.io/api/errors#rate_limited"
		},
		"recheck_limit_reached": {
			"type": "rate_limited",
			"status": 429,
			"description": "Too many rechecks on this project today.",
			"doc_url": "https://docs.rustine.io/api/errors#recheck_limit_reached"
		},
		"repository_analyses_limit_reached": {
			"type": "rate_limited",
			"status": 429,
			"description": "The workspace started many repository analyses in the last 24 hours.",
			"doc_url": "https://docs.rustine.io/api/errors#repository_analyses_limit_reached"
		},
		"platform_busy": {
			"type": "rate_limited",
			"status": 503,
			"description": "Rustine is not starting new scans for a few minutes: retry later.",
			"doc_url": "https://docs.rustine.io/api/errors#platform_busy"
		},
		"recheck_failed": {
			"type": "api_error",
			"status": 502,
			"description": "The recheck could not complete (site unreachable?).",
			"doc_url": "https://docs.rustine.io/api/errors#recheck_failed"
		},
		"service_unavailable": {
			"type": "api_error",
			"status": 503,
			"description": "Rustine is unavailable for a moment: retry with the same Idempotency-Key.",
			"doc_url": "https://docs.rustine.io/api/errors#service_unavailable"
		},
		"internal_error": {
			"type": "api_error",
			"status": 500,
			"description": "Something went wrong on Rustine's side; it is reported automatically.",
			"doc_url": "https://docs.rustine.io/api/errors#internal_error"
		}
	}
}
