{"openapi":"3.0.0","info":{"title":"EasySummit Agent API","description":"Token-authenticated /api/v1 surface for agent-driven speaker, project, roster and field-schema management. Generated from code via zircote/swagger-php — this document is the single source of truth for the contract (CON-01).","version":"1.0.0"},"servers":[{"url":"/","description":"Current host"}],"paths":{"/api/v1/projects/{project_id}/appearances":{"get":{"tags":["appearances"],"summary":"List a project's speaker roster.","description":"Requires the `appearances:read` scope. Unpaginated — bounded per-project cardinality.","operationId":"c736e838d54caa4fa0bd1529aa5da83c","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The project's roster.","content":{"application/json":{"schema":{"properties":{"appearances":{"type":"array","items":{"type":"object"}}},"type":"object"}}}},"403":{"description":"Missing appearances:read scope (error.insufficient_scope)."}}},"post":{"tags":["appearances"],"summary":"Attach an existing library speaker to a project's roster.","description":"Requires the `appearances:write` scope. Audits with actor_type=api_token.","operationId":"46f130c1dfd2e6bbe58328d899715a19","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"speaker_id":{"type":"string","format":"uuid"}},"type":"object"}}}},"responses":{"201":{"description":"The speaker's appearance in this project.","content":{"application/json":{"schema":{"properties":{"speaker":{"type":"object"},"appearance":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing appearances:write scope (error.insufficient_scope)."},"404":{"description":"error.speaker_not_found"},"409":{"description":"error.already_attached"},"422":{"description":"error.speaker_id_required"}}}},"/api/v1/appearances/{appearance_id}":{"delete":{"tags":["appearances"],"summary":"Remove a speaker from the roster (reversible soft-delete).","description":"Requires the `appearances:write` scope. Audits with actor_type=api_token.","operationId":"63f00dae2a63d76bcb4ddfb9b862565b","parameters":[{"name":"appearance_id","in":"path","description":"Appearance UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Removal acknowledged.","content":{"application/json":{"schema":{"properties":{"deleted":{"type":"boolean"}},"type":"object"}}}},"403":{"description":"Missing appearances:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found"}}}},"/api/v1/projects/{project_id}/appearances/order":{"put":{"tags":["appearances"],"summary":"Reorder a project's roster.","description":"Requires the `appearances:write` scope.","operationId":"b0885782c6c640af749d873fd357f9cd","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"order":{"type":"array","items":{"type":"string","format":"uuid"}}},"type":"object"}}}},"responses":{"200":{"description":"Reorder acknowledged.","content":{"application/json":{"schema":{"properties":{"reordered":{"type":"boolean"}},"type":"object"}}}},"403":{"description":"Missing appearances:write scope (error.insufficient_scope)."},"422":{"description":"error.invalid_order"}}}},"/api/v1/billing/subscription":{"get":{"tags":["billing"],"summary":"Get the tenant's current subscription status.","description":"Requires the `billing:read` scope. Read-only mirror of the session subscription view.","operationId":"cd913d183290fdc53056b9841bc7b54e","responses":{"200":{"description":"The current subscription.","content":{"application/json":{"schema":{"properties":{"subscription":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing billing:read scope (error.insufficient_scope)."},"404":{"description":"error.subscription_not_found"}}}},"/api/v1/billing/invoices":{"get":{"tags":["billing"],"summary":"List invoices for the current tenant.","description":"Requires the `billing:read` scope.","operationId":"d835a419d2faeed128c04ed54ef12df1","parameters":[{"name":"limit","in":"query","description":"Page size (default 50, max 200).","schema":{"type":"integer"}},{"name":"offset","in":"query","description":"Pagination offset.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated invoice rows.","content":{"application/json":{"schema":{"properties":{"data":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing billing:read scope (error.insufficient_scope)."}}}},"/api/v1/billing/invoices/{invoice_id}":{"get":{"tags":["billing"],"summary":"Get a single invoice.","description":"Requires the `billing:read` scope.","operationId":"0218c688a0dcf3a2d792f7b68e38873a","parameters":[{"name":"invoice_id","in":"path","description":"Invoice UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The invoice record.","content":{"application/json":{"schema":{"properties":{"invoice":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing billing:read scope (error.insufficient_scope)."},"404":{"description":"error.not_found"}}}},"/api/v1/billing/usage":{"get":{"tags":["billing"],"summary":"Get month-to-date AI token / image-credit usage and effective quotas.","description":"Requires the `billing:read` scope.","operationId":"23905500e45233497bc209f160a43198","responses":{"200":{"description":"Usage + quota telemetry.","content":{"application/json":{"schema":{"properties":{"current_month":{"type":"object"},"thirty_day_rollup_tokens":{"type":"array","items":{"type":"object"}},"thirty_day_rollup_credits":{"type":"array","items":{"type":"object"}},"usd_estimates":{"type":"object"},"prices":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing billing:read scope (error.insufficient_scope)."}}}},"/api/v1/enrichment/rewrites":{"post":{"tags":["enrichment"],"summary":"Trigger an AI bio rewrite (async — poll the returned status_url).","description":"Requires the `enrichment:write` scope. Audits with actor_type=api_token. Enqueues an `ai.bio_rewrite` job and returns immediately — the vendor call happens in the worker, never in this request.","operationId":"86201ba706e8f7eba1f8427d2d352fd0","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"appearance_id":{"type":"string","format":"uuid"},"field":{"type":"string","enum":["bio_long","bio_short","bio_tagline"]},"tone":{"type":"string","enum":["professional","conversational","punchy"]}},"type":"object"}}}},"responses":{"202":{"description":"Rewrite enqueued.","content":{"application/json":{"schema":{"properties":{"result_id":{"type":"string","format":"uuid"},"status_url":{"type":"string"}},"type":"object"}}}},"402":{"description":"quota_exhausted (flat shape, not the wrapped envelope)."},"403":{"description":"Missing enrichment:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found (foreign/unknown appearance_id)."},"422":{"description":"error.validation"}}}},"/api/v1/enrichment/rewrites/{result_id}":{"get":{"tags":["enrichment"],"summary":"Poll an AI bio-rewrite result until status reaches ready|failed.","description":"Requires the `enrichment:read` scope. Reads the domain status row directly.","operationId":"204bd5eb53784393db03eca20c1ef79b","parameters":[{"name":"result_id","in":"path","description":"Rewrite result UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Rewrite result (status: pending|ready|failed)."},"403":{"description":"Missing enrichment:read scope (error.insufficient_scope)."},"404":{"description":"error.not_found"}}}},"/api/v1/enrichment/rewrites/{result_id}/apply":{"post":{"tags":["enrichment"],"summary":"Apply a ready AI rewrite result onto the appearance override.","description":"Requires the `enrichment:write` scope. Audits with actor_type=api_token.","operationId":"fc45cf1a319617d4822ea05c922c2f08","parameters":[{"name":"result_id","in":"path","description":"Rewrite result UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"appearance_id":{"type":"string","format":"uuid"}},"type":"object"}}}},"responses":{"200":{"description":"{\"applied\": true}"},"400":{"description":"invalid_status:* / invalid_field:*"},"403":{"description":"Missing enrichment:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found (foreign/unknown result_id or appearance mismatch)."}}}},"/api/v1/enrichment/headshots":{"post":{"tags":["enrichment"],"summary":"Upload a headshot for AI background removal (async — poll GET /assets/{asset_id}).","description":"Requires the `enrichment:write` scope. Audits with actor_type=api_token. Multipart upload: `speaker_id` (form field) + `file`. Runs the SAME UploadValidator (magic-byte + polyglot kill) + AvScanner chain as the session upload path, unweakened. Enqueues `process.headshot` and returns immediately — the vendor call happens in the worker.","operationId":"e6e618ec4077ff7a8c33a723caec4754","responses":{"202":{"description":"Headshot accepted, processing enqueued.","content":{"application/json":{"schema":{"properties":{"asset_id":{"type":"string","format":"uuid"},"processing_status":{"type":"string","example":"pending"},"status_url":{"type":"string"}},"type":"object"}}}},"400":{"description":"asset_upload_missing_file / asset_upload_unreadable / upload rejected (magic-byte, polyglot, virus)."},"403":{"description":"Missing enrichment:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found (foreign/unknown speaker_id)."},"422":{"description":"error.validation"},"503":{"description":"av_scanner_unavailable (fail-closed)."}}}},"/api/v1/enrichment/headshots/{asset_id}/reprocess":{"post":{"tags":["enrichment"],"summary":"Reprocess (re-run bg-removal on) a previously uploaded headshot.","description":"Requires the `enrichment:write` scope. Audits with actor_type=api_token. Enqueues `process.headshot` against the cached raw bytes — returns immediately.","operationId":"d7225b843bf3211cbba4e943948a90be","parameters":[{"name":"asset_id","in":"path","description":"Asset UUID (must be a parent/original row).","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"properties":{"subject_hint":{"type":"string","maxLength":80}},"type":"object"}}}},"responses":{"202":{"description":"Reprocessing enqueued."},"400":{"description":"not_reprocessable (targeted a variant, not the parent original)."},"402":{"description":"quota_exhausted (flat shape)."},"403":{"description":"Missing enrichment:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found"},"422":{"description":"error.validation"}}}},"/api/v1/assets/{asset_id}":{"get":{"tags":["enrichment"],"summary":"Poll an asset (headshot) until processing_status reaches done|failed.","description":"Requires the `enrichment:read` scope. Reads the domain status row directly.","operationId":"60aa26b663f140567da71f5c31978642","parameters":[{"name":"asset_id","in":"path","description":"Asset UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Asset (processing_status: pending|done|failed)."},"403":{"description":"Missing enrichment:read scope (error.insufficient_scope)."},"404":{"description":"error.not_found"}}}},"/api/v1/schema/fields":{"get":{"tags":["schema"],"summary":"Read the tenant custom field-definition schema.","description":"Requires the `schema:read` scope (a DEDICATED read-only reference scope, NOT folded into speakers:read). Returns the full, unpaginated set of field definitions — each definition carries the `key` that `custom_fields` on a speaker is nested under, enabling an agent to build a speaker form without any speaker-PII scope.","operationId":"0a39e9a212e7e1f3ffcb4edaa5b89040","responses":{"200":{"description":"The tenant field-definition schema.","content":{"application/json":{"schema":{"properties":{"definitions":{"description":"Each definition carries key/label/type/sort_order — the keys custom_fields uses.","type":"array","items":{"type":"object"}},"version":{"description":"sha256 of the definitions JSON, for cache invalidation.","type":"string"}},"type":"object"}}}},"403":{"description":"Missing schema:read scope (error.insufficient_scope)."}}}},"/api/v1/imports":{"post":{"tags":["imports"],"summary":"Start a CSV import from an uploaded file (multipart).","description":"Requires the `imports:write` scope. Multipart form: `project_id` field + `file` field (CSV, max 10 MB).","operationId":"695b5db67df87c951c29ddc58d45c341","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"properties":{"project_id":{"type":"string","format":"uuid"},"file":{"type":"string","format":"binary"}},"type":"object"}}}},"responses":{"201":{"description":"Import staged.","content":{"application/json":{"schema":{"properties":{"import_id":{"type":"string","format":"uuid"},"headers":{"type":"array","items":{"type":"string"}},"suggested_mapping":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing imports:write scope (error.insufficient_scope)."},"413":{"description":"error.csv_upload_too_large (>10 MB)."},"422":{"description":"error.validation / error.csv_upload_missing_file"}}}},"/api/v1/imports/text":{"post":{"tags":["imports"],"summary":"Start a CSV import from pasted text.","description":"Requires the `imports:write` scope.","operationId":"5f7715bc398e9155a178bcf780fab32b","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"project_id":{"type":"string","format":"uuid"},"text":{"description":"Pasted tab/comma/semicolon-separated text.","type":"string"}},"type":"object"}}}},"responses":{"201":{"description":"Import staged.","content":{"application/json":{"schema":{"properties":{"import_id":{"type":"string","format":"uuid"},"headers":{"type":"array","items":{"type":"string"}},"suggested_mapping":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing imports:write scope (error.insufficient_scope)."},"413":{"description":"error.csv_upload_too_large (>10 MB)."},"422":{"description":"error.paste_import_empty"}}}},"/api/v1/imports/{import_id}/mapping":{"put":{"tags":["imports"],"summary":"Set the CSV column → speaker-field mapping for a staged import.","description":"Requires the `imports:write` scope.","operationId":"f9cf92354e513ab3919bc397eb9dc99c","parameters":[{"name":"import_id","in":"path","description":"Import UUID from upload/paste.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"mapping":{"description":"CSV header → speaker field key.","type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"Mapping saved.","content":{"application/json":{"schema":{"properties":{"mapped":{"type":"boolean"}},"type":"object"}}}},"403":{"description":"Missing imports:write scope (error.insufficient_scope)."},"404":{"description":"error.csv_import_not_found"}}}},"/api/v1/imports/{import_id}/preview":{"get":{"tags":["imports"],"summary":"Preview the staged rows for a mapped import.","description":"Requires the `imports:read` scope.","operationId":"00ff46fa49d360ebb32db701c74c687b","parameters":[{"name":"import_id","in":"path","description":"Import UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Per-row preview + aggregate counts.","content":{"application/json":{"schema":{"properties":{"import_id":{"type":"string","format":"uuid"},"rows":{"type":"array","items":{"type":"object"}},"total":{"type":"integer"},"valid_count":{"type":"integer"},"invalid_count":{"type":"integer"},"duplicate_in_project_count":{"type":"integer"}},"type":"object"}}}},"403":{"description":"Missing imports:read scope (error.insufficient_scope)."},"404":{"description":"error.csv_import_not_found"}}}},"/api/v1/imports/{import_id}/commit":{"post":{"tags":["imports"],"summary":"Commit the previewed rows of an import.","description":"Requires the `imports:write` scope. Enqueues batched `speaker.import.rows` jobs; poll `/progress` for completion.","operationId":"1621a4fa5ea9eab279c1350ae5562977","parameters":[{"name":"import_id","in":"path","description":"Import UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"rows":{"description":"{row_id, decision?, policy?, duplicate_in_project?}[]","type":"array","items":{"type":"object"}},"default_policy":{"type":"string","nullable":true}},"type":"object"}}}},"responses":{"200":{"description":"Commit enqueued; poll /progress for completion.","content":{"application/json":{"schema":{"properties":{"import_id":{"type":"string","format":"uuid"},"accepted_count":{"type":"integer"},"rejected_in_project_count":{"type":"integer"},"batches":{"type":"integer"}},"type":"object"}}}},"403":{"description":"Missing imports:write scope (error.insufficient_scope)."},"404":{"description":"error.csv_import_not_found"}}}},"/api/v1/imports/{import_id}/progress":{"get":{"tags":["imports"],"summary":"Poll an import's commit progress.","description":"Requires the `imports:read` scope.","operationId":"e253715af65c74a800bbf55545d083e1","parameters":[{"name":"import_id","in":"path","description":"Import UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Import progress.","content":{"application/json":{"schema":{"properties":{"import_id":{"type":"string","format":"uuid"},"total_rows":{"type":"integer"},"committed_rows":{"type":"integer"},"failed_rows":{"type":"integer"},"status":{"type":"string"}},"type":"object"}}}},"403":{"description":"Missing imports:read scope (error.insufficient_scope)."},"404":{"description":"error.csv_import_not_found"}}}},"/api/v1/openapi.json":{"get":{"tags":["meta"],"summary":"The generated OpenAPI 3.x contract for the /api/v1 agent API.","description":"GET /api/v1/openapi.json — the generated OpenAPI document.\n\nPublic, unauthenticated, unwrapped. Scans this directory's controller\nattributes on each request and returns the decoded top-level document.","operationId":"13bc9fb54763ddd3437cfb96461a479f","responses":{"200":{"description":"The generated OpenAPI document (unwrapped top-level object).","content":{"application/json":[]}}}}},"/api/v1/projects":{"get":{"tags":["projects"],"summary":"List the tenant's projects.","description":"Requires the `projects:read` scope. Unpaginated — bounded per-tenant cardinality.","operationId":"bb83e056e9c007c1ad4de743dc41fe1f","responses":{"200":{"description":"The tenant's projects.","content":{"application/json":{"schema":{"properties":{"projects":{"type":"array","items":{"type":"object"}}},"type":"object"}}}},"403":{"description":"Missing projects:read scope (error.insufficient_scope)."}}},"post":{"tags":["projects"],"summary":"Create a project.","description":"Requires the `projects:write` scope. Audits with actor_type=api_token.","operationId":"7da2278319b47d930a286451b84a7fdb","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"starts_at":{"type":"string"},"ends_at":{"type":"string"},"timezone":{"type":"string"},"status":{"type":"string","enum":["active","archived"]}},"type":"object"}}}},"responses":{"201":{"description":"The created project.","content":{"application/json":{"schema":{"properties":{"project":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing projects:write scope (error.insufficient_scope)."},"422":{"description":"error.validation"}}}},"/api/v1/projects/{project_id}":{"get":{"tags":["projects"],"summary":"Get a single project.","description":"Requires the `projects:read` scope.","operationId":"9b6013e3f341d0d9968764e5821de9bd","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The project record.","content":{"application/json":{"schema":{"properties":{"project":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing projects:read scope (error.insufficient_scope)."},"404":{"description":"error.not_found"}}},"patch":{"tags":["projects"],"summary":"Update a project.","description":"Requires the `projects:write` scope. Audits with actor_type=api_token.","operationId":"fb5f6f75807d5a5934805dfe9aaf2d5c","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"starts_at":{"type":"string"},"ends_at":{"type":"string"},"timezone":{"type":"string"},"status":{"type":"string","enum":["active","archived"]},"publication_target_id":{"type":"string"}},"type":"object"}}}},"responses":{"200":{"description":"Update acknowledged.","content":{"application/json":{"schema":{"properties":{"updated":{"type":"boolean"}},"type":"object"}}}},"403":{"description":"Missing projects:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found"},"422":{"description":"error.validation"}}}},"/api/v1/publication/targets":{"get":{"tags":["publication"],"summary":"List publication targets (capabilities + outbox counts, secrets excluded).","description":"Requires the `publication:read` scope. The response never contains `config` or any decrypted adapter secret.","operationId":"18f948d3a4320a726568a9b68658ad2b","responses":{"200":{"description":"Active publication targets.","content":{"application/json":{"schema":{"properties":{"targets":{"type":"array","items":{"type":"object"}}},"type":"object"}}}},"403":{"description":"Missing publication:read scope (error.insufficient_scope)."}}},"post":{"tags":["publication"],"summary":"Create a publication target.","description":"Requires the `publication:write` scope. Audits with actor_type=api_token. The response never contains `config`.","operationId":"853d1daf09cfd9a090bda0251444cc5f","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"name":{"type":"string"},"adapter_type":{"type":"string"},"config":{"type":"object"}},"type":"object"}}}},"responses":{"201":{"description":"The created target (config excluded).","content":{"application/json":{"schema":{"properties":{"target":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing publication:write scope (error.insufficient_scope)."},"422":{"description":"error.validation"}}}},"/api/v1/publication/capabilities/matrix":{"get":{"tags":["publication"],"summary":"Resolve the per-target/per-field capability matrix.","description":"Requires the `publication:read` scope.","operationId":"f49bdddefbc53a78763881bcbc8bbc9e","parameters":[{"name":"targetIds","in":"query","description":"Comma-separated target UUIDs (max 20).","schema":{"type":"string"}},{"name":"fieldKeys","in":"query","description":"Comma-separated field keys.","schema":{"type":"string"}}],"responses":{"200":{"description":"The capability matrix."},"403":{"description":"Missing publication:read scope (error.insufficient_scope)."},"422":{"description":"error.validation (too many targetIds)."}}}},"/api/v1/publication/dry-run":{"post":{"tags":["publication"],"summary":"Preview a publish payload for one appearance across targets (side-effect-free).","description":"Requires the `publication:read` scope. This is a READ operation — it writes nothing, so it is deliberately NOT confirm-gated (unlike the live publish routes below).","operationId":"c6daf0e756f3a75b162c5a61b5bf9d25","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"appearance_id":{"type":"string","format":"uuid"},"target_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"event_type":{"type":"string"}},"type":"object"}}}},"responses":{"200":{"description":"Preview result — no side effects."},"403":{"description":"Missing publication:read scope (error.insufficient_scope)."},"422":{"description":"error.validation"}}}},"/api/v1/projects/{project_id}/publish":{"post":{"tags":["publication"],"summary":"LIVE publish the whole project roster to its linked target (destructive/outward — confirm-gated).","description":"Requires the `publication:write` scope. Audits with actor_type=api_token. MUST include `{\"confirm\": true}` in the body or the request returns 428 error.confirmation_required and enqueues nothing. Do NOT reuse the same Idempotency-Key across the unconfirmed probe and the confirmed retry — send Idempotency-Key only on the final confirmed call.","operationId":"71ace346f3e24b35d44facd630ccc5fd","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"confirm":{"description":"Must be strictly `true`.","type":"boolean"}},"type":"object"}}}},"responses":{"200":{"description":"Queued count."},"403":{"description":"Missing publication:write scope (error.insufficient_scope)."},"422":{"description":"error.no_publication_target"},"428":{"description":"error.confirmation_required — resend with confirm:true."}}}},"/api/v1/projects/{project_id}/appearances/{appearance_id}/publish":{"post":{"tags":["publication"],"summary":"LIVE publish one appearance to the project's linked target (destructive/outward — confirm-gated).","description":"Requires the `publication:write` scope. Audits with actor_type=api_token. MUST include `{\"confirm\": true}` in the body or the request returns 428 error.confirmation_required and enqueues nothing. Do NOT reuse the same Idempotency-Key across the unconfirmed probe and the confirmed retry — send Idempotency-Key only on the final confirmed call.","operationId":"df1711225c1a1f2f0e1aad37c0be3039","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"appearance_id","in":"path","description":"Appearance UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"confirm":{"description":"Must be strictly `true`.","type":"boolean"}},"type":"object"}}}},"responses":{"200":{"description":"Queued."},"403":{"description":"Missing publication:write scope (error.insufficient_scope)."},"404":{"description":"error.appearance_not_found / error.project_not_found"},"422":{"description":"error.no_publication_target"},"428":{"description":"error.confirmation_required — resend with confirm:true."}}}},"/api/v1/speakers":{"get":{"tags":["speakers"],"summary":"List speakers in the tenant library (cursor-paginated).","description":"Requires the `speakers:read` scope.","operationId":"e39a443988ecc2a4c7866ff4e9c914dc","parameters":[{"name":"q","in":"query","description":"Free-text search over name/bio.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size (default 50).","schema":{"type":"integer"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a prior response.","schema":{"type":"string"}},{"name":"category","in":"query","description":"Filter by category.","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated speaker rows.","content":{"application/json":{"schema":{"properties":{"rows":{"type":"array","items":{"type":"object"}},"next_cursor":{"type":"string","nullable":true}},"type":"object"}}}},"403":{"description":"Missing speakers:read scope (error.insufficient_scope)."}}}},"/api/v1/speakers/{speaker_id}":{"get":{"tags":["speakers"],"summary":"Get a single speaker, incl. custom fields.","description":"Requires the `speakers:read` scope.","operationId":"ec8b5d22d485b406bd2cbb8f0a17c430","parameters":[{"name":"speaker_id","in":"path","description":"Speaker library UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The speaker record.","content":{"application/json":{"schema":{"properties":{"speaker":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing speakers:read scope (error.insufficient_scope)."},"404":{"description":"error.not_found"}}},"delete":{"tags":["speakers"],"summary":"Soft-delete a speaker from the library (irreversible; confirm-gated).","description":"Requires the `speakers:write` scope. Destructive: the body MUST carry `{\"confirm\": true}` — an unconfirmed call returns 428 error.confirmation_required and deletes nothing. Audits with actor_type=api_token. IDEMPOTENCY-KEY CAVEAT (do NOT reuse a key across the unconfirmed probe and the confirmed retry): IdempotencyMiddleware hashes method+path+body, so a key seen with a different body is rejected 409 error.idempotency_key_conflict — send an Idempotency-Key ONLY on the final confirmed call.","operationId":"65117dd8b4364429820d2ac78b5c3e5a","parameters":[{"name":"speaker_id","in":"path","description":"Speaker library UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"confirm":{"description":"MUST be boolean true to execute the delete (API-05 confirmation contract).","type":"boolean"}},"type":"object"}}}},"responses":{"200":{"description":"Speaker soft-deleted.","content":{"application/json":{"schema":{"properties":{"deleted":{"type":"boolean"}},"type":"object"}}}},"403":{"description":"Missing speakers:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found"},"409":{"description":"error.idempotency_key_conflict (Idempotency-Key reused with a different body)."},"428":{"description":"error.confirmation_required (body lacked confirm:true; nothing was deleted)."}}},"patch":{"tags":["speakers"],"summary":"Update a speaker's library record, incl. custom fields.","description":"Requires the `speakers:write` scope. Audits with actor_type=api_token.","operationId":"6ce528c651cd67ed4abcb169fd15041b","parameters":[{"name":"speaker_id","in":"path","description":"Speaker library UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string"},"bio_long":{"type":"string"},"bio_short":{"type":"string"},"bio_tagline":{"type":"string"},"custom_fields":{"description":"Free-form, keyed by tenant field-definition key.","type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"The updated speaker record.","content":{"application/json":{"schema":{"properties":{"speaker":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing speakers:write scope (error.insufficient_scope)."},"404":{"description":"error.not_found"},"422":{"description":"error.validation"}}}},"/api/v1/projects/{project_id}/speakers":{"post":{"tags":["speakers"],"summary":"Create a speaker in a project (library entry + first appearance).","description":"Requires the `speakers:write` scope. Audits with actor_type=api_token.","operationId":"c750a3eb7b07d5a562145f20ecbc1ca7","parameters":[{"name":"project_id","in":"path","description":"Project UUID.","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"name":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string"},"bio_long":{"type":"string"},"bio_short":{"type":"string"},"bio_tagline":{"type":"string"},"custom_fields":{"description":"Free-form, keyed by tenant field-definition key.","type":"object"}},"type":"object"}}}},"responses":{"201":{"description":"The created speaker + its first appearance.","content":{"application/json":{"schema":{"properties":{"speaker":{"type":"object"},"appearance":{"type":"object"}},"type":"object"}}}},"403":{"description":"Missing speakers:write scope (error.insufficient_scope)."},"422":{"description":"error.name_required / error.validation"}}}},"/api/v1/tokens/whoami":{"get":{"tags":["tokens"],"summary":"Identity of the calling token (prefix, scopes, tenant/user ids).","description":"Requires the `tokens:read` scope. Never returns the token_hash or the raw value (TOK-07).","operationId":"b09da9c854f7a9d8503b1cf61898d114","responses":{"200":{"description":"The calling token's safe identity.","content":{"application/json":{"schema":{"properties":{"id":{"type":"string","format":"uuid","nullable":true},"token_prefix":{"type":"string","nullable":true},"scopes":{"type":"array","items":{"type":"string"}},"tenant_id":{"type":"string","format":"uuid","nullable":true},"user_id":{"type":"string","format":"uuid","nullable":true}},"type":"object"}}}},"403":{"description":"Missing tokens:read scope (error.insufficient_scope)."}}}},"/api/v1/tokens/rotate":{"post":{"tags":["tokens"],"summary":"Rotate the calling token: mint a successor + revoke self, atomically.","description":"Requires the `tokens:manage` scope. Returns the successor's raw es_live_ value exactly once and echoes the revoked predecessor id. Successor + self-revoke run in one transaction so a mid-rotation failure leaves the original valid (TOK-04, T-09-08-02).","operationId":"27662bdb33b261204797b571e66da5b6","responses":{"201":{"description":"The minted successor token (raw value shown once) + the revoked predecessor id.","content":{"application/json":{"schema":{"properties":{"token":{"description":"The successor's raw es_live_ value — shown exactly once.","type":"string"},"token_row":{"description":"The successor's safe metadata row.","type":"object"},"revoked_token_id":{"type":"string","format":"uuid"}},"type":"object"}}}},"401":{"description":"error.unauthorized (no token / incomplete identity)."},"403":{"description":"Missing tokens:manage scope (error.insufficient_scope)."}}}}},"tags":[{"name":"appearances","description":"Agent-facing speaker↔project roster management (API-02)."},{"name":"billing","description":"Agent-facing billing reads (API-08). READ-ONLY — there is no billing:write."},{"name":"enrichment","description":"Agent-facing AI bio-rewrite + headshot processing trigger/poll (API-06)."},{"name":"schema","description":"Agent-facing read-only field schema (API-03)."},{"name":"imports","description":"Agent-facing CSV/text import wizard (API-07)."},{"name":"projects","description":"Agent-facing project CRUD (API-02)."},{"name":"publication","description":"Agent-facing publication targets, capability matrix, dry-run, and confirm-gated live publish (API-04/API-05)."},{"name":"speakers","description":"Agent-facing speaker CRUD (API-01)."},{"name":"tokens","description":"Agent token self-management (whoami, rotate) — TOK-04."},{"name":"meta","description":"meta"}]}