{"openapi":"3.1.0","info":{"title":"Skimle API","version":"1.0.0","description":"Public REST API for reading and writing project data on Skimle.\n\n## Authentication\n\nEvery request needs an `Authorization: Bearer <api-key>` header.\n\nA key carries two independent properties: its **scope** and its\n**access rights**.\n\nScope decides which endpoints the key reaches, and the two are not\ninterchangeable — presenting the wrong one is a 401, not reduced access:\n\n- **project-scoped** — one key, exactly one project.\n  `/api/skimle-read` and `/api/skimle-write`.\n- **account-scoped** — one key, one team account.\n  `/api/skimle-account`, which creates projects and therefore cannot\n  be bound to one. The account decides which account pays and where\n  `create_project` lands; what the key can REACH is every project the\n  user who created it is a member of, whichever account owns them. An\n  edit key acts read-only on a project where that membership is not\n  `owner` or `admin`.\n\nAccess rights are marked by the final character:\n\n- `skm_…r` — **read** key\n- `skm_…e` — **edit** key (required for any write, and for every\n  `/api/skimle-account` action)\n\nCreate keys at\n[https://sandbox.skimle.com/home/api-keys](https://sandbox.skimle.com/home/api-keys).\nThe full key value is shown **once** at creation — copy it then.\nSkimle only stores the hash, so a key that wasn't saved at\ncreation is gone for good.\n\n### Partner secret keys\n\n`/api/skimle-partner` uses a different credential entirely: a\n**partner secret key** (`skmp_…`), issued out of band by Skimle rather\nthan minted in the app. It provisions end users, creates projects on\ntheir accounts, and bills the partner account for every document\nprocessed — so it is not self-service, and it is not interchangeable\nwith an API key in either direction.\n\nA partner may touch only the accounts and projects it created. Anything\nelse answers 404, including projects that exist but were created some\nother way.\n\n## Rate limits\n\n- `/api/skimle-read` — **60 requests / minute / project**\n- `/api/skimle-write` — **30 requests / minute / project**\n- `/api/skimle-account` — **30 requests / minute / account**\n- `/api/skimle-account/upload` — **10 requests / minute / account**\n- `/api/skimle-partner` — **60 requests / minute / partner account**\n- `/api/skimle-partner/upload` — **20 requests / minute / partner account**\n\n## Short IDs\n\nMost ID fields accept three formats interchangeably:\n\n- UUID — `e07ca2f6-…`\n- Counter-based short ID — `D3k` (document), `I205y` (insight)\n- Prefixed hash — `C1a2b3c4` (category), `T5d6e7f8` (tag)\n\n`/api/skimle-read` and `/api/skimle-write` RETURN the short forms —\nprefixed hashes and counter IDs, never raw UUIDs — because those\nresponses are written to be read.\n\n`/api/skimle-account` and `/api/skimle-partner` return raw UUIDs\ninstead. They hand back ids you feed straight into the next call in a\nsequence, and account and user ids have no short form to resolve, so a\nshortened id there would be one you could not use. Both still ACCEPT\nthe `P`-prefixed project form on input."},"servers":[{"url":"https://sandbox.skimle.com","description":"Default (uses the host that served this docs page)"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Read","description":"Query project data — projects, documents, insights, categories, tags, notes, memos, metadata, plus semantic search and document chunk reading."},{"name":"Write","description":"Mutate project data — create / update / delete insights, manage categories and tags, create notes, batch-add insights with quote verification."},{"name":"Partner","description":"Partner integration — provision end users, create projects on their accounts and seed them with documents. Requires a PARTNER SECRET KEY (skmp_…), issued out of band by Skimle. A partner may touch only the accounts and projects it created, and its own account pays for every document processed."},{"name":"Account","description":"Project lifecycle — create a project, upload documents, confirm them (which charges credits), and start an analysis. Requires an ACCOUNT-scoped edit key rather than a project-scoped one, because the first action creates the project the others act on."}],"paths":{"/api/skimle-read":{"post":{"tags":["Read"],"summary":"Read project data","description":"Single dispatcher endpoint. The request body picks an action via the `action` discriminator. Each action has its own body shape — see the `oneOf` below.","operationId":"skimleRead","requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"type":"object","properties":{"action":{"type":"string","const":"get_project"}},"required":["action"],"additionalProperties":false,"title":"get_project"},{"type":"object","properties":{"action":{"type":"string","const":"get_documents"},"id":{"type":"string","maxLength":200},"filters":{"type":"object","properties":{"tags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"categories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"metadata":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":200,"description":"The field name, matched EXACTLY against the stored name (its short name first, then its full one). Deliberately stricter than `value` beside it: a field is a column a caller can read back from `get_metadata`, where a value is free text somebody typed."},"value":{"type":"string","maxLength":1000,"description":"The value, matched case-, whitespace- and trailing-punctuation-insensitively against both value columns, so `yes`, `Yes` and `Yes.` all match a column holding any of them. A blank literal matches NOTHING rather than everything."}},"required":["field","value"],"additionalProperties":false}},"documentIds":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"excludeCategories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"excludeTags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}}},"additionalProperties":false}},"required":["action"],"additionalProperties":false,"title":"get_documents"},{"type":"object","properties":{"action":{"type":"string","const":"get_insights"},"id":{"type":"string","maxLength":200},"ids":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","maxLength":200}},"filters":{"type":"object","properties":{"tags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"categories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"metadata":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":200,"description":"The field name, matched EXACTLY against the stored name (its short name first, then its full one). Deliberately stricter than `value` beside it: a field is a column a caller can read back from `get_metadata`, where a value is free text somebody typed."},"value":{"type":"string","maxLength":1000,"description":"The value, matched case-, whitespace- and trailing-punctuation-insensitively against both value columns, so `yes`, `Yes` and `Yes.` all match a column holding any of them. A blank literal matches NOTHING rather than everything."}},"required":["field","value"],"additionalProperties":false}},"documentIds":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"excludeCategories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"excludeTags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}}},"additionalProperties":false}},"required":["action"],"additionalProperties":false,"title":"get_insights"},{"type":"object","properties":{"action":{"type":"string","const":"get_categories"},"id":{"type":"string","maxLength":200},"filters":{"type":"object","properties":{"tags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"categories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"metadata":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":200,"description":"The field name, matched EXACTLY against the stored name (its short name first, then its full one). Deliberately stricter than `value` beside it: a field is a column a caller can read back from `get_metadata`, where a value is free text somebody typed."},"value":{"type":"string","maxLength":1000,"description":"The value, matched case-, whitespace- and trailing-punctuation-insensitively against both value columns, so `yes`, `Yes` and `Yes.` all match a column holding any of them. A blank literal matches NOTHING rather than everything."}},"required":["field","value"],"additionalProperties":false}},"documentIds":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"excludeCategories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"excludeTags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}}},"additionalProperties":false}},"required":["action"],"additionalProperties":false,"title":"get_categories"},{"type":"object","properties":{"action":{"type":"string","const":"get_memos"},"id":{"type":"string","maxLength":200}},"required":["action"],"additionalProperties":false,"title":"get_memos"},{"type":"object","properties":{"action":{"type":"string","const":"get_tags"}},"required":["action"],"additionalProperties":false,"title":"get_tags"},{"type":"object","properties":{"action":{"type":"string","const":"get_notes"},"filters":{"type":"object","properties":{"tags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"categories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"metadata":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":200,"description":"The field name, matched EXACTLY against the stored name (its short name first, then its full one). Deliberately stricter than `value` beside it: a field is a column a caller can read back from `get_metadata`, where a value is free text somebody typed."},"value":{"type":"string","maxLength":1000,"description":"The value, matched case-, whitespace- and trailing-punctuation-insensitively against both value columns, so `yes`, `Yes` and `Yes.` all match a column holding any of them. A blank literal matches NOTHING rather than everything."}},"required":["field","value"],"additionalProperties":false}},"documentIds":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"excludeCategories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"excludeTags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}}},"additionalProperties":false}},"required":["action"],"additionalProperties":false,"title":"get_notes"},{"type":"object","properties":{"action":{"type":"string","const":"get_metadata"}},"required":["action"],"additionalProperties":false,"title":"get_metadata"},{"type":"object","properties":{"action":{"type":"string","const":"search"},"query":{"type":"string","minLength":1,"maxLength":8000},"queries":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","minLength":1,"maxLength":8000}},"topK":{"type":"integer","minimum":1,"maximum":50},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991},"onlyInsights":{"type":"boolean"},"filters":{"type":"object","properties":{"tags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"categories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"metadata":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":200,"description":"The field name, matched EXACTLY against the stored name (its short name first, then its full one). Deliberately stricter than `value` beside it: a field is a column a caller can read back from `get_metadata`, where a value is free text somebody typed."},"value":{"type":"string","maxLength":1000,"description":"The value, matched case-, whitespace- and trailing-punctuation-insensitively against both value columns, so `yes`, `Yes` and `Yes.` all match a column holding any of them. A blank literal matches NOTHING rather than everything."}},"required":["field","value"],"additionalProperties":false}},"documentIds":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"excludeCategories":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"excludeTags":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}}},"additionalProperties":false}},"required":["action"],"additionalProperties":false,"title":"search"},{"type":"object","properties":{"action":{"type":"string","const":"read_document"},"documentId":{"type":"string","maxLength":200},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","minimum":1,"maximum":30}},"required":["action","documentId"],"additionalProperties":false,"title":"read_document"},{"type":"object","properties":{"action":{"type":"string","const":"find_entities"},"entity":{"type":"string","maxLength":500}},"required":["action"],"additionalProperties":false,"title":"find_entities"},{"type":"object","properties":{"action":{"type":"string","const":"analyze"},"type":{"type":"string","enum":["metadata_distribution","temporal"]},"categoryIds":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}},"categoryNames":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":200}}},"required":["action","type"],"additionalProperties":false,"title":"analyze"},{"type":"object","properties":{"action":{"type":"string","const":"verify_context"},"type":{"type":"string","enum":["documents","insights","all"]},"from":{"type":"integer","minimum":1,"maximum":9007199254740991},"to":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["action"],"additionalProperties":false,"title":"verify_context"}]},"examples":{"get-categories-tree":{"summary":"List the category tree","value":{"action":"get_categories"}},"get-insights-filtered":{"summary":"List insights in a category, excluding those tagged \"Resolved\"","value":{"action":"get_insights","filters":{"categories":["Onboarding"],"excludeTags":["Resolved"]}}},"search":{"summary":"Semantic search","value":{"action":"search","query":"pricing pain points","topK":10}},"read-document":{"summary":"Read a document in pages of chunks","value":{"action":"read_document","documentId":"D3k","offset":0,"limit":20}}}}}},"responses":{"200":{"description":"Action-specific result. Shape varies per `action` — see the action descriptions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — a read key was presented, or the key's user holds a role below `owner`/`admin` on the named project and may therefore only call `get_project_status` there.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/skimle-write":{"post":{"tags":["Write"],"summary":"Mutate project data","description":"Single dispatcher endpoint. The request body picks an action via the `action` discriminator. Each action has its own body shape — see the `oneOf` below. Requires an edit-scoped API key (suffix `e`).\n\n`start_analysis` is deprecated here: use the same action on `/api/skimle-account`, where `inductive` is spelled `automatic` and `predefined` is also available.","operationId":"skimleWrite","requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"type":"object","properties":{"action":{"type":"string","const":"manage_insights"},"operation":{"type":"string","enum":["create","update","delete"]},"id":{"type":"string","maxLength":200},"insightIds":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":200}},"documentId":{"type":"string","maxLength":200},"text":{"type":"string","minLength":1,"maxLength":2000},"description":{"type":"string","maxLength":4000},"quotes":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":4000}},"categories":{}},"required":["action","operation","categories"],"additionalProperties":false,"title":"manage_insights"},{"type":"object","properties":{"action":{"type":"string","const":"manage_categories"},"operation":{"type":"string","enum":["create","update","delete","merge"]},"id":{"type":"string","maxLength":200},"text":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","maxLength":4000},"parentId":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"categoryName":{"type":"string","maxLength":500},"deleteIfNotEmpty":{"type":"boolean"},"categoryIds":{"minItems":2,"maxItems":100,"type":"array","items":{"type":"string","maxLength":200}},"newName":{"type":"string","minLength":1,"maxLength":500}},"required":["action","operation"],"additionalProperties":false,"title":"manage_categories"},{"type":"object","properties":{"action":{"type":"string","const":"categorize_insights"},"operation":{"type":"string","enum":["assign","move","remove"]},"insightIds":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string","maxLength":200}},"categoryId":{"type":"string","maxLength":200}},"required":["action","operation","insightIds","categoryId"],"additionalProperties":false,"title":"categorize_insights"},{"type":"object","properties":{"action":{"type":"string","const":"manage_tags"},"operation":{"type":"string","enum":["create","update","delete","add_targets","remove_targets"]},"id":{"type":"string","maxLength":200},"text":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","maxLength":4000},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"documentIds":{"maxItems":1000,"type":"array","items":{"type":"string","maxLength":200}},"insightIds":{"maxItems":1000,"type":"array","items":{"type":"string","maxLength":200}},"categoryIds":{"maxItems":1000,"type":"array","items":{"type":"string","maxLength":200}}},"required":["action","operation"],"additionalProperties":false,"title":"manage_tags"},{"type":"object","properties":{"action":{"type":"string","const":"create_note"},"targetType":{"type":"string","enum":["document","documentInsight","insightCategory"]},"targetId":{"type":"string","maxLength":200},"text":{"type":"string","minLength":1,"maxLength":10000}},"required":["action","targetType","targetId","text"],"additionalProperties":false,"title":"create_note"},{"type":"object","properties":{"action":{"type":"string","const":"manage_metadata_fields"},"operation":{"type":"string","enum":["create","update","delete"]},"id":{"type":"string","maxLength":200},"text":{"type":"string","minLength":1,"maxLength":500},"shortName":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":4000},"parentId":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"valueType":{"type":"string","enum":["authors","date","language","number","organization"]},"allowedValues":{"maxItems":200,"type":"array","items":{"type":"string","minLength":1,"maxLength":500}},"answerSetOpen":{"type":"boolean"},"fieldName":{"type":"string","maxLength":500},"deleteIfNotEmpty":{"type":"boolean"}},"required":["action","operation"],"additionalProperties":false,"title":"manage_metadata_fields"},{"type":"object","properties":{"action":{"type":"string","const":"set_metadata_values"},"documentId":{"type":"string","maxLength":200},"values":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"fieldId":{"type":"string","maxLength":200},"text":{"type":"string","minLength":1,"maxLength":2000},"shortName":{"type":"string","minLength":1,"maxLength":200},"value":{}},"required":["fieldId","text"],"additionalProperties":false}}},"required":["action","documentId","values"],"additionalProperties":false,"description":"A field whose answers the project has declared accepts only those answers unless the set is open: a value it does not cover is stored as empty, listed in the response’s `refused` array, and recorded on the row so the editor can say which value was not accepted. A value it does cover is stored in the set’s own spelling, and `shortName` is resolved the same way — a label the set refuses is not stored either, because the two columns describe one answer.","title":"set_metadata_values"},{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"type":{"type":"string","enum":["inductive","automatic","summaries","metadata_summaries"]},"strictMode":{"type":"boolean"},"analysisGranularity":{"type":"string","enum":["default","inclusive","overview"]},"reconsiderSkipped":{"type":"boolean"}},"required":["action","type"],"additionalProperties":false,"deprecated":true,"description":"Deprecated: use `start_analysis` on POST /api/skimle-account, which also offers `type: \"predefined\"`. That endpoint requires an ACCOUNT-scoped edit key, which a team owner mints under Settings → External access.","title":"start_analysis"},{"type":"object","properties":{"action":{"type":"string","const":"start_predefined_analysis"},"themes":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","maxLength":4000}},"required":["name"],"additionalProperties":false}},"name":{"type":"string","maxLength":500},"documentScope":{"default":"all","description":"Which documents to analyse. Pass 'filtered' whenever you pass metadataFilter — a filter sent with 'all' is refused rather than silently analysing the whole project.","type":"string","enum":["all","filtered"]},"metadataFilter":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":500},"value":{"type":"string","maxLength":2000}},"required":["field","value"],"additionalProperties":false}}},"required":["action","themes","documentScope"],"additionalProperties":false,"title":"start_predefined_analysis"}]},"examples":{"create-insight":{"summary":"Create an insight on one document in a subcategory","value":{"action":"manage_insights","operation":"create","documentId":"D3k","text":"Cost is a barrier to adoption","quotes":["it's just too expensive","we couldn't afford it"],"description":"Multiple respondents cited cost as the main blocker.","categories":["C1a2b3c4"]}},"categorize-assign":{"summary":"Assign several insights into an additional category","value":{"action":"categorize_insights","operation":"assign","insightIds":["I205y","I340z"],"categoryId":"C1a2b3c4"}},"manage-categories-merge":{"summary":"Merge two categories into a new one","value":{"action":"manage_categories","operation":"merge","categoryIds":["C1a2b3c4","Cabcd1234"],"newName":"Pricing & Affordability"}},"tags-add":{"summary":"Apply a tag to several insights","value":{"action":"manage_tags","operation":"add_targets","id":"T5d6e7f8","insightIds":["I205y","I340z","I420a"]}},"delete-insights-bulk":{"summary":"Delete a list of insights in one call","value":{"action":"manage_insights","operation":"delete","insightIds":["I205y","I340z"]}}}}}},"responses":{"200":{"description":"Action-specific result. Shape varies per `action` — see the action descriptions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — for example, a read-only key was used on /skimle-write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/skimle-write/upload":{"post":{"tags":["Write"],"summary":"Upload documents to the key's project (deprecated)","description":"Deprecated: use `POST /api/skimle-account/upload?projectId=…`, which requires an ACCOUNT-scoped edit key that a team owner mints under Settings → External access.\n\nUploads into the project the edit key is bound to; the body never names a project. Accepts the same bodies as its successor, up to 20 files per request and 50 MB each. Documents land at `pending` and are NOT charged until they are confirmed.","operationId":"skimleWriteUpload","deprecated":true,"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"array","items":{"type":"string","format":"binary"}}}}},"application/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string"},"filename":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"The documents created, at `pending` status.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — for example, a read-only key was used on /skimle-write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/skimle-account":{"post":{"tags":["Account"],"summary":"Project lifecycle","description":"Single dispatcher endpoint for the create → upload → confirm → analyse sequence. Requires an ACCOUNT-scoped edit API key (suffix `e`); a project-scoped key is refused with 401. Every action except `create_project` names a project, which must be one the key's user is a member of — a project nobody added them to answers 404 exactly as a non-existent one does, whoever owns it. Where that membership is not `owner` or `admin` the key acts read-only: `get_project_status` still works, and every other action is refused with 403.\n\nTypical flow:\n\n1. `create_project`\n2. `POST /api/skimle-account/upload?projectId=…` with the files\n3. `get_project_status` — poll until documents reach `awaiting_confirmation`\n4. `confirm_documents` with the reported `confirmableCreditCost` — this CHARGES CREDITS\n\nOne `confirm_documents` call covers one pipeline, because the anonymise setting that prices a document belongs to its pipeline. `get_project_status` reports `confirmableBatches`, one entry per pipeline with its own `documentIds` and `creditCost`; omitting `documentIds` confirms the single batch, and is refused with 409 when there is more than one.\n5. `start_analysis`, either `automatic` or `predefined` with a `categories` array\n\nSteps 3 and 4 are separate because a freshly uploaded document has no price until the worker has extracted its text.\n\n**Polling.** Wait on `pendingExtractionCount` reaching 0, not on every document reaching `awaiting_confirmation`. A document whose text extracted to nothing — an empty spreadsheet, say — finishes at stage `empty`: it priced at zero, it is never confirmable, and it is TERMINAL. Those ids are listed as `emptyDocumentIds`, and `delete_documents` is the only thing that clears them.","operationId":"skimleAccount","requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"type":"object","properties":{"action":{"type":"string","const":"create_project"},"name":{"type":"string","minLength":1,"maxLength":255},"locale":{"type":"string","enum":["en","ar","zh-CN","zh-TW","da","fi","fr","de","he","it","ja","ko","lt","no","pt","es","sv"]},"description":{"type":"string","maxLength":2000}},"required":["action","name"],"additionalProperties":false,"title":"create_project"},{"type":"object","properties":{"action":{"type":"string","const":"get_project_status"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."}},"required":["action","projectId"],"additionalProperties":false,"title":"get_project_status"},{"type":"object","properties":{"action":{"type":"string","const":"confirm_documents"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"documentIds":{"maxItems":10000,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}},"expectedCreditCost":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["action","projectId","expectedCreditCost"],"additionalProperties":false,"title":"confirm_documents"},{"type":"object","properties":{"action":{"type":"string","const":"delete_documents"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"documentIds":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}}},"required":["action","projectId","documentIds"],"additionalProperties":false,"title":"delete_documents"},{"oneOf":[{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"type":{"type":"string","const":"predefined"},"categories":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"instructions":{"type":"string","maxLength":4000},"categorizationInstructions":{"type":"string","maxLength":4000},"examples":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":2000}}},"required":["name"],"additionalProperties":false}},"analysisGranularity":{"description":"`default` (chunk × theme matrix), `overview` (top chunks per document) or `inclusive` (the per-document full scan). Applies to the thematic types only. Omitted, `default`.","type":"string","enum":["default","inclusive","overview"]}},"required":["action","projectId","type","categories"],"additionalProperties":false},{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"type":{"type":"string","const":"automatic"},"analysisGranularity":{"description":"`default` (chunk × theme matrix), `overview` (top chunks per document) or `inclusive` (the per-document full scan). Applies to the thematic types only. Omitted, `default`.","type":"string","enum":["default","inclusive","overview"]},"reconsiderSkipped":{"description":"On an existing automatic analysis only: clear the skipped and failed verdicts it holds before enqueueing, so the run reconsiders those documents. Analysed documents are never re-run. The response carries the number cleared and per-state coverage counts.","type":"boolean"}},"required":["action","projectId","type"],"additionalProperties":false},{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"type":{"type":"string","enum":["summaries","metadata_summaries"]},"analysisGranularity":{"description":"`default` (chunk × theme matrix), `overview` (top chunks per document) or `inclusive` (the per-document full scan). Applies to the thematic types only. Omitted, `default`.","type":"string","enum":["default","inclusive","overview"]}},"required":["action","projectId","type"],"additionalProperties":false}],"title":"start_analysis"}]},"examples":{"create-project":{"summary":"Create a project","value":{"action":"create_project","name":"Q3 customer interviews","locale":"en"}},"project-status":{"summary":"Check which documents are ready to confirm, and what they cost","value":{"action":"get_project_status","projectId":"P1a2b3c4"}},"confirm-documents":{"summary":"Confirm every document awaiting confirmation (charges credits)","value":{"action":"confirm_documents","projectId":"P1a2b3c4","expectedCreditCost":1840}},"delete-documents":{"summary":"Drop documents that will never be confirmable (no undelete)","value":{"action":"delete_documents","projectId":"P1a2b3c4","documentIds":["D9z8y7x6"]}},"start-automatic":{"summary":"Start an automatic analysis that discovers its own themes","value":{"action":"start_analysis","projectId":"P1a2b3c4","type":"automatic"}},"start-predefined":{"summary":"Start an analysis against categories you supply","value":{"action":"start_analysis","projectId":"P1a2b3c4","type":"predefined","categories":[{"name":"Pricing objections","description":"Anything framing cost as a reason not to adopt.","examples":["It is just too expensive for a team our size"]},{"name":"Onboarding friction","instructions":"Include only first-run experience, not ongoing usability complaints."}]}}}}}},"responses":{"200":{"description":"Action-specific result. Shape varies per `action` — see the action descriptions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — for example, a read-only key was used on /skimle-write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/skimle-account/upload":{"post":{"tags":["Account"],"summary":"Upload documents to a project","description":"Accepts either `multipart/form-data` with one or more `file` fields, or `application/json` of the form `{ \"urls\": [{ \"url\": \"…\", \"filename\": \"…\" }] }` which the server fetches. Up to 20 files per request, 50 MB each. Media files are not accepted here.\n\nDocuments land at `pending` and are NOT charged — poll `get_project_status` and then `confirm_documents` to pay for them and start processing.","operationId":"skimleAccountUpload","parameters":[{"name":"projectId","in":"query","required":true,"description":"The project to upload into — the P-prefixed short id or the raw uuid. Must be a project the key's user is a member of, with an `owner` or `admin` role — an upload is a write, so a lesser role is refused with 403.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"array","items":{"type":"string","format":"binary"}}}}},"application/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string"},"filename":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"The documents created, at `pending` status.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — a read key was presented, or the key's user holds a role below `owner`/`admin` on the named project. An upload always writes, so there is no read exemption.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/skimle-partner":{"post":{"tags":["Partner"],"summary":"Partner integration","description":"Single dispatcher endpoint for provisioning end users and seeding their projects. Requires a PARTNER SECRET KEY (`skmp_…`) in the Authorization header; project and account API keys are refused with 401.\n\n**What a partner may touch.** Exactly what it created. Accounts and projects minted here are recorded against the partner's account id, and every action naming one re-reads that record. An id with no record, or one naming another partner, answers 404 exactly as a non-existent id does.\n\n**Who pays.** Every charge on this surface is drawn from the partner's account. With `onboard: true` (the default) `create_account` also puts the end user on the free trial plan, and they receive its initial credits for their own in-app use, including confirming documents the partner seeded; the API never spends those. Pass `onboard: false` to provision the account without a plan and leave the user to onboard and choose one themselves.\n\nTypical flow:\n\n1. `create_account` — invite the user, provision their team account\n2. `create_project` — on that account, optionally with seed files\n3. `get_project_status` — poll until documents reach `awaiting_confirmation`\n4. `confirm_documents` — charges the PARTNER's credits\n5. `start_analysis`\n\nPoll on `pendingExtractionCount` reaching 0. A document that extracted to no text finishes at stage `empty` — priced at zero, never confirmable and terminal; `emptyDocumentIds` lists them and `delete_documents` clears them.","operationId":"skimlePartner","requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"type":"object","properties":{"action":{"type":"string","const":"create_account"},"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","minLength":1,"maxLength":255},"teamName":{"type":"string","minLength":1,"maxLength":255},"onboard":{"default":true,"description":"Default true: put the new team on the free trial plan (with that plan's initial credits) and mark the user onboarded, so they skip the onboarding funnel. The trial credits are the end user's, for their own in-app use, including confirming documents the partner seeded; this API never spends them. false: provision the account and leave the user to onboard and choose a plan themselves.","type":"boolean"}},"required":["action","email","onboard"],"additionalProperties":false,"title":"create_account"},{"type":"object","properties":{"action":{"type":"string","const":"list_accounts"}},"required":["action"],"additionalProperties":false,"title":"list_accounts"},{"type":"object","properties":{"action":{"type":"string","const":"list_projects"}},"required":["action"],"additionalProperties":false,"title":"list_projects"},{"type":"object","properties":{"action":{"type":"string","const":"create_project"},"accountId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"An account id returned by create_account or list_accounts."},"name":{"type":"string","minLength":1,"maxLength":255},"locale":{"type":"string","enum":["en","ar","zh-CN","zh-TW","da","fi","fr","de","he","it","ja","ko","lt","no","pt","es","sv"]},"description":{"type":"string","maxLength":2000},"files":{"type":"object","properties":{"urls":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri"},"filename":{"type":"string","minLength":1,"maxLength":500}},"required":["url"],"additionalProperties":false}},"documents":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":500},"text":{"type":"string","minLength":1}},"required":["filename","text"],"additionalProperties":false}}},"additionalProperties":false}},"required":["action","accountId","name"],"additionalProperties":false,"title":"create_project"},{"type":"object","properties":{"action":{"type":"string","const":"add_documents"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"files":{"type":"object","properties":{"urls":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri"},"filename":{"type":"string","minLength":1,"maxLength":500}},"required":["url"],"additionalProperties":false}},"documents":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":500},"text":{"type":"string","minLength":1}},"required":["filename","text"],"additionalProperties":false}}},"additionalProperties":false}},"required":["action","projectId","files"],"additionalProperties":false,"title":"add_documents"},{"type":"object","properties":{"action":{"type":"string","const":"get_project_status"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."}},"required":["action","projectId"],"additionalProperties":false,"title":"get_project_status"},{"type":"object","properties":{"action":{"type":"string","const":"confirm_documents"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"documentIds":{"maxItems":10000,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}},"expectedCreditCost":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["action","projectId","expectedCreditCost"],"additionalProperties":false,"title":"confirm_documents"},{"type":"object","properties":{"action":{"type":"string","const":"delete_documents"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"documentIds":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}}},"required":["action","projectId","documentIds"],"additionalProperties":false,"title":"delete_documents"},{"oneOf":[{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"type":{"type":"string","const":"predefined"},"categories":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"instructions":{"type":"string","maxLength":4000},"categorizationInstructions":{"type":"string","maxLength":4000},"examples":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":2000}}},"required":["name"],"additionalProperties":false}},"analysisGranularity":{"description":"`default` (chunk × theme matrix), `overview` (top chunks per document) or `inclusive` (the per-document full scan). Applies to the thematic types only. Omitted, `default`.","type":"string","enum":["default","inclusive","overview"]}},"required":["action","projectId","type","categories"],"additionalProperties":false},{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"type":{"type":"string","const":"automatic"},"analysisGranularity":{"description":"`default` (chunk × theme matrix), `overview` (top chunks per document) or `inclusive` (the per-document full scan). Applies to the thematic types only. Omitted, `default`.","type":"string","enum":["default","inclusive","overview"]},"reconsiderSkipped":{"description":"On an existing automatic analysis only: clear the skipped and failed verdicts it holds before enqueueing, so the run reconsiders those documents. Analysed documents are never re-run. The response carries the number cleared and per-state coverage counts.","type":"boolean"}},"required":["action","projectId","type"],"additionalProperties":false},{"type":"object","properties":{"action":{"type":"string","const":"start_analysis"},"projectId":{"type":"string","minLength":1,"maxLength":255,"description":"Project id — the P-prefixed short id or the raw uuid."},"type":{"type":"string","enum":["summaries","metadata_summaries"]},"analysisGranularity":{"description":"`default` (chunk × theme matrix), `overview` (top chunks per document) or `inclusive` (the per-document full scan). Applies to the thematic types only. Omitted, `default`.","type":"string","enum":["default","inclusive","overview"]}},"required":["action","projectId","type"],"additionalProperties":false}],"title":"start_analysis"}]},"examples":{"create-account":{"summary":"Provision an end user (idempotent for this partner)","value":{"action":"create_account","email":"researcher@university.example","name":"Alex Researcher"}},"create-project-with-urls":{"summary":"Create a project and seed it from URLs","value":{"action":"create_project","accountId":"0f6b2a4e-1c3d-4f5a-8b7c-9d0e1f2a3b4c","name":"Autumn fieldwork","locale":"en","files":{"urls":[{"url":"https://files.example/interview-1.docx"},{"url":"https://files.example/interview-2.docx"}]}}},"create-project-with-inline-text":{"summary":"Create a project and seed it with transcript text inline","value":{"action":"create_project","accountId":"0f6b2a4e-1c3d-4f5a-8b7c-9d0e1f2a3b4c","name":"Autumn fieldwork","files":{"documents":[{"filename":"interview-1.txt","text":"Interviewer: How did you find the service?\nRespondent: ..."}]}}},"add-documents":{"summary":"Add more documents to a project this partner created","value":{"action":"add_documents","projectId":"P1a2b3c4","files":{"documents":[{"filename":"interview-3.txt","text":"..."}]}}},"confirm-documents":{"summary":"Confirm everything awaiting confirmation (charges the partner)","value":{"action":"confirm_documents","projectId":"P1a2b3c4","expectedCreditCost":1840}},"delete-documents":{"summary":"Drop documents that will never be confirmable (no undelete)","value":{"action":"delete_documents","projectId":"P1a2b3c4","documentIds":["D9z8y7x6"]}},"start-predefined":{"summary":"Analyse against categories the partner supplies","value":{"action":"start_analysis","projectId":"P1a2b3c4","type":"predefined","categories":[{"name":"Access barriers"},{"name":"Trust in staff"}]}}}}}},"responses":{"200":{"description":"Action-specific result. Shape varies per `action` — see the action descriptions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — for example, a read-only key was used on /skimle-write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/skimle-partner/upload":{"post":{"tags":["Partner"],"summary":"Upload documents to a partner project (multipart)","description":"The multipart upload path. The other two — fetched `urls` and inline `documents` — ride the JSON dispatcher as `create_project.files` / `add_documents.files`; this one cannot, because a multipart body has no place in a discriminated union.\n\nThe project must be one this partner created. Documents land at `pending` and are not charged until `confirm_documents`.","operationId":"skimlePartnerUpload","parameters":[{"name":"projectId","in":"query","required":true,"description":"A project this partner created — P-prefixed short id or raw uuid.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"The documents created, at `pending` status.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error (invalid JSON or schema mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — for example, a read-only key was used on /skimle-write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Read: 60 req/min/project. Write: 30 req/min/project. Project: 30 req/min/account (10 for uploads). Partner: 60 req/min/partner account (20 for uploads).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"skm_…r (read) or skm_…e (edit) for the API-key surfaces, or skmp_… for /api/skimle-partner. Project-scoped keys authorise /api/skimle-read and /api/skimle-write; account-scoped keys authorise /api/skimle-account; partner secret keys authorise /api/skimle-partner. None of the three is interchangeable with another."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable message."},"details":{"description":"Optional structured detail — present on 400 responses with the per-field validation errors."}},"additionalProperties":false}}}}